Skip to content

Commit eac950d

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-17356-reachability-root-enumeration
2 parents 00ac98d + b9598e9 commit eac950d

409 files changed

Lines changed: 33835 additions & 4805 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`liveness/agent.json`, `liveness/skill.json` and `liveness/action.json` — the 21 cloud citations these ledgers rest on now carry the date they were read and the symbol they were read at, and the two claims that reading falsified are corrected in the prose (#13272).
6+
7+
The ledgers ship inside this package, so the pointers an upgrading reader follows are these. Until now they named a package root and nothing else: `cloud: packages/service-ai/src/agent-runtime.ts`, with no date and — after #13309 repointed them off a path that existed in neither repository — still no evidence that anybody had opened the file. Every row was re-read in a cloud checkout at cloud `@cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8` and now carries `verifiedAt: 2026-09-15`, `evidenceScope: "cross-repo"`, and a `#symbol` anchor on the consuming function.
8+
9+
- **A symbol instead of a line, because a line rots in range.** Three of the cited line numbers had already drifted onto unrelated prose (`agent-runtime.ts:264`, `agent-access.ts:50`, `action-tools.ts:535`) while every mechanical check kept passing. A symbol moves with the consumer and goes red when the consumer is renamed or deleted.
10+
- **The framework half is now gate-checked.** `packages/mcp/src/skill-prompts.ts#projectSkillPrompt` is a repo-local anchor in five skill rows — the `;` before it ends the `cloud` realm's scope — so `check:liveness` resolves it against the file on every run, where the old parenthesised `(projectSkillPrompt)` was prose no check read. Cloud anchors are counted, never resolved, which is why the date on them is load-bearing.
11+
- **Two ledger assertions were false and are repaired.** `agent.role` was noted as *"persona → system prompt."*: it reaches `AgentSummary` through `listAgents` and nothing else — `buildSystemMessages` never reads it. `agent.planning` was cited at `agent-runtime.ts`, which does not read the key at all; its three readers are `routes/agent-routes.ts`, `routes/assistant-routes.ts` and `eval/eval-runner.ts`.
12+
- **One row is deliberately left unstamped.** `agent.tools` was falsified by the same read — zero consumers in cloud, and this package's own `AgentSchema` already declares the key `retiredKey(...)`. Its verdict is a liveness re-grade rather than a stamping decision, filed separately as #18304; a `verifiedAt` there would certify the wrong thing.
13+
14+
No verdict moved and no schema changed: this is the evidence layer of the ledger, and `check:liveness` reports the same 505 repo-local paths resolving as before with five more anchors now checked.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
'@objectstack/platform-objects': patch
4+
'@objectstack/spec': patch
5+
'@objectstack/core': patch
6+
'@objectstack/cli': patch
7+
---
8+
9+
docs(identity): re-point the cloud-identity `ADR-0024` citations at the records that decide them (#14361)
10+
11+
From this repository's point of view `ADR-0024` names two unrelated decisions.
12+
`docs/adr/0024-mcp-connectors.md` is *MCP Servers as Connectors* — an open,
13+
vendor-neutral tool protocol, with a Decision section numbered §1–§5 and no
14+
D-lettered clauses at all. The identity surface's citations mean something else
15+
entirely: the identity-and-access decision taken in `objectstack-ai/cloud` as
16+
its own ADR-0024, whose open mechanism half has been mirrored into this repo
17+
since 2026-09-07 as
18+
[ADR-0135](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0135-identity-and-access-architecture.md).
19+
A reader following one of those citations landed on a real page about the wrong
20+
subject, which is worse than a dangling id: a plausible-looking record invites
21+
belief rather than a second question.
22+
23+
79 citation lines were read one at a time and re-pointed. 73 mean a clause
24+
ADR-0135 restates and now name it with its letter — D4 (source-of-truth marking,
25+
managed vs env-native), D5.2 (the break-glass last-administrator invariant), D6
26+
(SSO per production environment, including the opt-in DNS domain-verification
27+
clause this tree spelled `ADR-0024 ②`) and D9 (environment users and
28+
organization membership). 6 mean a clause ADR-0135 deliberately leaves in the
29+
cloud record and now carry the anchors gate's cross-repo qualifier
30+
`cloud ADR-0024`: `V1` (the SSO default-role provisioning, the roadmap and
31+
commercial framing) and `§7` (the `ai_seat` synthesis, which ADR-0135 does not
32+
restate).
33+
34+
What actually reaches a consumer of these packages:
35+
36+
- `@objectstack/plugin-auth` — the **operator-facing break-glass refusal
37+
detail** now reads `break-glass invariant, ADR-0135 D5.2 — an environment must
38+
always keep at least one administrator who can sign in`. The condition that
39+
raises it, its status, its error code and the rest of its wording are
40+
unchanged; only the ADR number moves. ⚠️ A deployment that greps that message
41+
for the literal `ADR-0024` should grep for `ADR-0135`. The guard's
42+
registration log line moves the same way.
43+
- `@objectstack/platform-objects` — `sys_sso_provider`'s `domain_verified` field
44+
help text, its `protection.reason`, and the matching leaf in all four shipped
45+
locale bundles (`en`, `es-ES`, `ja-JP`, `zh-CN`).
46+
- `@objectstack/spec` — the doc comment above `AuthConfigSchema`'s
47+
`ssoDomainVerification`, published both in `dist/` and as
48+
`src/system/auth-config.zod.ts`.
49+
- `@objectstack/core`, `@objectstack/cli` — doc comments only, published in
50+
`dist/`; no runtime string and no behaviour.
51+
52+
No behaviour moves. No schema accepts or refuses anything it did not accept or
53+
refuse before, no security or permission semantics are touched, and no ADR
54+
record is written or edited. Bare `ADR-0024` still resolves exactly as it did:
55+
the 15 citations that mean the local MCP-connectors record are byte-identical to
56+
`main`, and `check:adr-anchors` reports the same resolving-citation totals before
57+
and after. Historical archives are deliberately untouched — 36 CHANGELOG lines
58+
across seven packages, and the 22 lines under `docs/adr/`, which is a governed
59+
surface this change does not enter.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@objectstack/service-automation": patch
3+
---
4+
5+
`try_catch`'s catch-region binding is annotated as the plain declared type. `TryCatchErrorValueSchema` declares `code: z.string().optional()`, so the local `TryCatchErrorValue & { code?: string }` intersection in `builtin/try-catch-node.ts` added nothing the exported `TryCatchErrorValue` did not already carry, and the comment paragraph beside it explained a spec/engine divergence that no longer exists (#15669).
6+
7+
**No behaviour change, and nothing executable moves.** The object literal is untouched: `nodeId`, `message`, `code` and `iteration` / `item` are bound under exactly the same conditions as before, so a catch region still branches on `{$error.code}` and still reads an absent `code` as "no classified code", never as "nothing failed". Measured on the built package: `index.js`, `index.cjs`, `index.d.ts` and `index.d.cts` are **byte-identical** before and after; only `index.js.map` / `index.cjs.map` shift (by one byte each), because the replacement comment is two lines longer and the sourcemap encodes line positions.
8+
9+
The annotation was proven redundant before it was removed — `TryCatchErrorValue` and `TryCatchErrorValue & { code?: string }` are mutually assignable, and `TryCatchErrorValue['code']` is exactly `string | undefined` — and the binding it describes is genuinely pinned: dropping `code` from the literal reddens the two `#14419` discriminator tests in `builtin/create-record-duplicate-code.test.ts`.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/plugin-sharing': patch
3+
---
4+
5+
docs(plugin-sharing): the `grantsRefused` subtype comment states the NARROWING, not a spec lag (#15712)
6+
7+
Two comments in this package described a spec/plugin lag that #14969 ended.
8+
`@objectstack/spec` now declares `grantsRefused?: number` on
9+
`SharingRuleEvaluationResult` itself, so "the six declared fields are unchanged"
10+
and "the contract lives in `@objectstack/spec` and is another lane's to move"
11+
read as if the spec were still behind. A reader reconciling the two would
12+
conclude the spec is missing a key it has.
13+
14+
No code moves. `SharingRuleReconcilePassResult extends SharingRuleEvaluationResult
15+
{ grantsRefused: number }` is a legal covariant narrowing before and after, and
16+
that narrowing is now what the prose says: the spec declares the key OPTIONAL on
17+
purpose — an `ISharingRuleService` implementation that does not count refusals
18+
leaves it ABSENT, and absent is not `0` — while this implementation always counts
19+
them and therefore requires it. The load-bearing paragraph is kept verbatim:
20+
`grantsRefused > 0` is NOT "the pass failed", it is the pass reporting that it met
21+
a record it cannot grant on and CONTINUED.
22+
23+
What reaches a consumer: doc comments, and only through the published
24+
`dist/index.d.ts` / `dist/index.d.mts`, where the JSDoc on the exported
25+
`SharingRuleReconcilePassResult` ships (705,069 to 705,528 bytes). The
26+
declaration-only projection of that file, comments stripped, is byte-identical
27+
before and after — no exported symbol added or removed, no key changed — and the
28+
JavaScript outputs (`dist/index.js`, `dist/index.mjs`) are untouched, because the
29+
compiler strips comments from them.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/core": minor
4+
---
5+
6+
feat(spec,core)!: the startup contract describes what the kernel produces — the orchestrator vocabulary is retired and `PluginStartupResult` is declared once (#16059)
7+
8+
<!-- adr-0087: registered startup-orchestrator-retired -->
9+
10+
**BREAKING** — a published exported surface is removed, landing in the launch window as
11+
`minor` (the lockstep convention: `major` is refused by `check-changeset-no-major`, and
12+
breaking-ness is carried by this banner plus the ADR-0087 disposition above).
13+
14+
`@objectstack/spec` declared a plugin startup ORCHESTRATOR that was never built, and its
15+
one shape that *is* real had drifted away from the kernel that produces it. The maintainer
16+
ruling on this card keeps a startup-result contract, and makes it describe what the kernel
17+
actually returns.
18+
19+
## What is removed
20+
21+
`IStartupOrchestrator` (`orchestrateStartup` / `rollback` / `checkHealth` /
22+
`startWithTimeout`) and the three schemas it tied together. Nothing in any repository
23+
implemented the interface and nothing parsed the schemas; `healthCheck` and `HealthStatus`
24+
named a per-plugin startup health probe the runtime has never had.
25+
26+
| removed | from | what to write instead |
27+
|:--|:--|:--|
28+
| `IStartupOrchestrator` | `@objectstack/spec/contracts` | nothing — plugin startup is the kernel's own boot loop |
29+
| `StartupOptionsSchema` / `StartupOptions` / `StartupOptionsParsed` | `@objectstack/spec/kernel`, `/contracts` | `startupTimeout` on the plugin; `rollbackOnFailure` on the kernel config |
30+
| `StartupOptions.healthCheck` | (with the schema) | **no replacement** — no startup probe system exists |
31+
| `HealthStatusSchema` / `HealthStatus` | `@objectstack/spec/kernel`, `/contracts` | **no replacement** — see above |
32+
| `StartupOrchestrationResultSchema` / `StartupOrchestrationResult` | `@objectstack/spec/kernel` | `ObjectKernel.getPluginStartupDurations()` |
33+
34+
`StartupOptions.parallel` and `StartupOptions.context` have no replacement either: the
35+
kernel starts plugins sequentially and passes its own `PluginContext`.
36+
37+
## What survives, re-declared
38+
39+
`PluginStartupResultSchema` / `PluginStartupResult` stay on both entries, rewritten to the
40+
shape `@objectstack/core` has always returned from `ObjectKernel.startPluginWithTimeout()`.
41+
`@objectstack/core` now **imports** that type instead of declaring a twin, so the two
42+
cannot drift again.
43+
44+
| member | before (spec) | after (spec and core, one declaration) |
45+
|:--|:--|:--|
46+
| `plugin: { name, version? }` | required | **removed** — write `pluginName: string` |
47+
| `pluginName` | absent | `string`, required |
48+
| `success` | `boolean`, required | unchanged |
49+
| `durationMs` | `number`, **required** | `number`, **optional** (absent when the plugin declares no `start()`) |
50+
| `startTime` | absent (it was core's own deprecated alias) | **removed** — read `durationMs`, which always carried the same value |
51+
| `error` | serializable projection | unchanged (a thrown `Error` satisfies it) |
52+
| `timedOut` | absent | `boolean`, optional — set when the failure was the timeout |
53+
| `health: HealthStatus` | optional | **removed** — no probe ever filled it |
54+
55+
**The one-line fix:** rename `plugin: { name }` to `pluginName`, delete `health`, and read
56+
`durationMs` wherever you read `startTime`. All three old spellings are `retiredKey()`
57+
tombstones on the surviving schema, so each is a `tsc` error at the construction site and a
58+
parse error carrying the prescription.
59+
60+
`startTime` is the one member whose removal a reader can OBSERVE: `@objectstack/core`
61+
populated it beside `durationMs` with the identical elapsed value, under its own ADR-0087
62+
L1 deprecation, and `ObjectKernel.startPluginWithTimeout()` stops setting it here. Mirroring
63+
it on the contract was the alternative and the tree refuses it — `check:duration-unit-keys`
64+
(ruling B on #14478) fails an elapsed number whose key name carries no unit, and neither of
65+
that rule's two schema-declared exemptions fits: it is not an `EpochMs` instant and it
66+
mirrors no external standard. Renaming it to `startTimeMs` would mint a spelling nothing has
67+
ever produced, for a member already documented as slated for removal.
68+
69+
For `@objectstack/core` consumers the members are unchanged; the one narrowing is that
70+
`PluginStartupResult.error` is now typed as the serializable projection
71+
(`name` / `message` / `stack?` / `code?`) rather than `Error`. The kernel still puts the
72+
thrown instance there, so `result.error instanceof Error` still narrows — only code that
73+
reads an `Error`-only member such as `cause` off it without that guard needs the guard.
74+
75+
## The retirement kit
76+
77+
Route 3 of the `spec-property-retirement` playbook: no authored document carried any of
78+
the three defs, so there is no seam for a D2 conversion and no author to hand a tombstone
79+
to. `RETIRED_DEFS_BY_MAJOR[18]` (`kernel/StartupOptions`, `kernel/HealthStatus`,
80+
`kernel/StartupOrchestrationResult`) plus the D3 semantic entry
81+
`startup-orchestrator-retired` **are** the declaration, and the three
82+
`json-schema.manifest/kernel.json` keys plus their 16 `authorable-surface/kernel.json`
83+
lines are deleted deliberately in this same change. The two keys of the SURVIVING result
84+
schema (`plugin`, `health`) take the tombstone route instead, registered in
85+
`RETIRED_KEYS_BY_MAJOR[18]`, because that def keeps emitting and its type is imported by
86+
`@objectstack/core`.
87+
88+
Runtime behaviour is deliberately unchanged: nothing ever read the retired surfaces, and
89+
the kernel boot loop is untouched.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@objectstack/plugin-approvals": patch
3+
---
4+
5+
`ApprovalService`'s privileged-override gate now resolves TENANT-admin standing from the ADR-0095 capability rung alone. Its tenant arm previously also admitted any principal whose `current_user.positions` contained the built-in identity names `org_owner` or `org_admin`, and a name on that array is not evidence of the capability behind it (#16166).
6+
7+
`positions[]` carries two different things at once: the ADR-0068 D2 **projection** of a membership role, whose source of truth is `sys_member.role`, and ADR-0057 D4 `sys_user_position` assignment values. A stored assignment row spelling one of those built-in names therefore arrived on the array with no org-administration grant behind it and satisfied the override gate anyway — for `decideNode`, `recall` and the console's participant-visibility read, within that organization. This is the tenant half of the same defect the platform arm of the same predicate had (#15981), and it lands the same way: **read the rung, never the name.**
8+
9+
- **The tenant rung is not the platform one.** ADR-0095 D3 resolves `TENANT_ADMIN` in `derivePosture` from the org-admin capability grants (`organization_admin` / `organization_admin_no_bypass`) and from nothing else, and those grants are what `packages/spec` declares that rung's source of truth. So the surviving two arms — the derived `posture` and the held capability — are one authority read in two spellings, kept apart only so a transport that never resolved `posture` still reads the grant.
10+
- **The #3424 stuck-approval escape hatch is unchanged** for anyone who actually holds org-admin standing: a genuine `organization_admin` grant still overrides, still only inside its own organization, and the decision is still audited as `via_override`.
11+
- **Who could notice.** A principal whose only claim to tenant-admin override was a stored `sys_user_position` row spelling `org_owner` / `org_admin` loses it. That row was never an assignment of the identity it spells — the platform refuses new ones on write — and the supported route to override standing is the org-admin capability grant, which the membership role provisions automatically for owners and admins.

0 commit comments

Comments
 (0)