You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679
Checklist run #20674 (acceptance card #12438, ADR-0126) scored clauses 2 and 3 of access-security.packaged-flow-write-door-parity (revision 1) as fail on main at 6bff748b (showcase, stock boot). Each was reproduced twice; clause 1 (the control leg) and clause 4 (no residue) passed.
Detail withheld pending maintainer, per docs/qa/platform-checklist/RUNNER.md rule 2 (the authentication/authorization carve-out). The reproduction stays with the runner session and was reported to the dispatching seat (domain:spec#5).
Full evidence chain for the rest of the run: #20674 (F-4).
Triage: first grade — bug · security · priority:p1 · domain:cli · area:access · pm:queue. Direction: the write door goes through the same locked-base check the metadata door applies
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T17:53Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: the write door is served by packages/runtime ⇒ domain:cli, by the lane table. The engine is domain:services's, and it is consulted, not edited. The reproduction stays withheld, as the runner filed it, and this grade adds no request detail.
What the public checklist item already states.access-security.packaged-flow-write-door-parity (docs/qa/platform-checklist/areas/access-security.json) describes the gap at the level of doors:
one automation write door accepts a change to a package-backed flow that the metadata door refuses under ADR-0126 §2's locked base;
its persona is an administrator who already holds manage_metadata.
Why p1, not p0.
It is reached and reproduced twice on main, so by the security rule it is at least p1.
It is not p0: the actor is an administrator already authorized to author metadata. No lower tier and no other organization reaches it, and no data outside that administrator's own authority is exposed.
The harm is integrity. A shipped package's locked base can be changed or removed in place through one door, which the lock exists to prevent.
Direction.
The automation write door (update and delete) calls the same locked-base predicate the metadata door uses, and answers the same ledgered refusal family. ⛔ Don't write a second lock implementation.
⛔ Don't put the lock inside the engine's registration: boot registers packaged flows through it legitimately.
Pins: the checklist item's own clauses 2 and 3 flip to PASS, with clause 1 (the control) and clause 4 (no residue) still passing.
Release: a promised lock that one door doesn't keep. Recommended before the next release, and it is on the triage seat's release list.
Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p1 · security, dispatched first at the maintainer's direction in this session's chat (「20679 20676 优先」)
Session: session_01VvcEokUG1tvVxkceYfR5XB
Account: huangyiirene
Branch: claude/issue-20679-automation-door-package-lock
Worktree: objectstack-issue-20679
Domain: domain:cli
Seat: domain:cli#1
File surface:
packages/runtime/src/domains/automation.ts: the PUT /:name and DELETE /:name arms only (the definition-write and removal doors), plus a new pin file beside it in packages/runtime/src/domains/.
A consumer of the same locked-base predicate the metadata door uses. It is imported and called from where it already lives. ⛔ No second lock implementation, and ⛔ no lock inside the engine's registerFlow / unregisterFlow (triage 5895656793: boot registers packaged flows through it).
Added in the same round (the pins the delivered diff needed):packages/qa/dogfood/test/packaged-flow-write-door-parity.dogfood.test.ts (the real-HTTP pin the dispatch order asked for, driving checklist clauses 1–4 over a booted showcase) and packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts (its derived population pins the protocol's verb count, which the new packagedBaseRefusal moves). Test files only; no other product file.
Added, cross-lane (the claiming seat carries it to landing):packages/metadata-protocol/src/protocol.ts: one public method, packagedBaseRefusal, on ObjectStackProtocolImplementation. It exposes the /meta door's own locked-base verdict. The private helpers it calls are lifted out unchanged, not copied. Its pins sit beside it, and @objectstack/metadata-protocol gets a minor changeset. The dispatch order told the dev to fix on the producer side; this claim's stop-line said to stop on a new export. The two texts disagreed, and that was the seat's error. The dev followed the order, and the seat adopts the surface here, with a cross-lane notice to domain:engine ([PM seat] domain:engine — 🟢 os-project-manager #6367).
If the predicate's real home is not importable from runtime without a new export, the dev stops and reports the producer side, ⛔ it does not copy the predicate.
Stop on breach and explain in the report
Container & model: M, mode:subagent, model: default tier (opus) (dispatch-gates --tier at 0d9349fe: no path-derived mandate; floor sonnet · default opus · ceiling fable). Security-boundary card, so it gets the judgment tier.
Clause-②: yes (widening)
Clause-② reading (re-read by the seat, amended in place): the refusal itself is a runtime authorization and integrity refusal, the negative boundary of clause ②. But the delivered diff adds one public method, packagedBaseRefusal, to the published ObjectStackProtocolImplementation (@objectstack/metadata-protocol). An added export member widens the public surface (references/lanes/spec.md: 「放宽接受集或扩大公开面的卡,不论多小,即条款②」), so the line is yes (widening): minor for @objectstack/metadata-protocol. No new error code; no packages/spec edit. The at-tier contract review is owed before enqueue.
Thread-read: 5895656793
Serial constraints cleared: #20726 (domain:services, claim 5903568667) holds the toggle route's DESCRIPTION region of packages/runtime/src/domains/automation.ts. That region is disjoint from the PUT and DELETE arms; only .objectui-sha is a single-claim path, so the later lander resolves any textual conflict. #20725 landed at 0d9349fe (PR #20759): it changed the engine's unregisterFlow (DELETE_RESTRICTED for a reachable packaged subflow), and this dispatch bases on it. None of the 10 open PRs touches automation.ts (file lists read in this act). #20676 (this seat, same round) edits dispatcher-plugin.ts and route-ledger.ts, disjoint files.
Taken 2026-09-30T03:49Z, main0d9349fe. The reproduction stays withheld as filed. The dev works from the public checklist item access-security.packaged-flow-write-door-parity (docs/qa/platform-checklist/areas/access-security.json) and the triage direction.
{
"issue": 20679,
"status": "done",
"branch": "claude/issue-20679-automation-door-package-lock",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20817",
"session": "session_01VvcEokUG1tvVxkceYfR5XB — the dispatching PM session's id (subagent run; same id as the parent, per the dispatch).",
"premise_still_valid": true,
"summary": "Verified on origin/main before editing: PUT and DELETE /automation/:name ran only the manage_metadata gate and went straight to registerFlow / unregisterFlow. The /meta door's locked-base verdict for a flow was private to ObjectStackProtocolImplementation: isArtifactBacked plus isOverlayAllowed. It is enforced in the protocol on environment kernels and by SysMetadataRepository.assertAllowed at the write on host-config kernels. It is not spec's evaluateLockFor* (that is the ADR-0010 per-item _lock) and not the permission-set lock. Fix: packages/metadata-protocol gains one public method, packagedBaseRefusal({ type, name, operation }). The seat adopted it as a cross-lane surface and re-declared the claim as Clause-② yes (widening). saveMetaItem's and deleteMetaItem's inline package doors are lifted verbatim into two private helpers that both methods still call; diff -w on the lifted bodies reads 0 changed lines (80/80 and 12/12). packages/runtime automation.ts asks that verdict with the flow NAME only, before the engine is called, and relays the refusal (403 NOT_OVERRIDABLE, the producer's ledgered code and sentence). It does this at PUT /:name, at DELETE /:name, and, as the seat-adopted bounded in-place fix, at POST / onto an existing name. POST / onto a packaged name measured 200 with an overwrite at 3690c44b before the fix. The lock sits ahead of the engine's DELETE_RESTRICTED / 409. Customer flows, clone, toggle and the OS_METADATA_WRITABLE hatch stay open. A dogfood pin over the booted showcase shows checklist clauses 1-4 passing.",
"tests": "Final head f5ea00604: every runtime automation-*.test.ts 25 files / 478 passed; runtime typecheck exit 0 (check:test-typecheck OK); protocol pins 27 passed; dogfood pin 5 passed after a runtime rebuild. At 3690c44b9 (merge over #20726): runtime local 292 files / 4215 passed, 1 skipped; runtime repo 727 passed; metadata-protocol 191 files passed, 3 skipped / 2801 passed, 19 skipped; typecheck exit 0; objectql, the 20 files pinning NOT_OVERRIDABLE / NOT_CREATABLE / ITEM_LOCKED, 362 passed; rest 3 files 41 passed. Ablations (commit first; scripts/ablation-replace.mjs with anchor hit and blob change; ablation-dist-preflight with the marker in dist for rebuilt legs; every restore proven by blob == HEAD, git diff HEAD empty, tree clean, marker absent): door helper disabled: runtime pin 7 red / 7 green, dogfood 3 red / 2 green; packagedBaseRefusal returns null (metadata-protocol rebuilt): protocol pin 6 red / 3 green, runtime pin 6 red / 8 green; re-raise discriminator widened: protocol pin 1 red / 8 green; POST call removed at f5ea00604: runtime pin 1 red / 14 green. All predicted before running; all pins green again after each restore.",
"mcp_calls": "0 — no MCP GitHub tool used. Reads were REST GETs (issue, comments, pulls, check-runs); two SendMessage replies went to the coordinator, which are not GitHub calls.",
"api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot], one repository_dispatch each: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20817, body read back byte-identical, 11499 bytes); (2) label-write assign → POST /repos/objectstack-ai/objectstack/issues/20817/assignees (huangyiirene, read back MATCHES); (3) this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/20679/comments. git push is not counted (it is not a REST write).",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: public door + exception: release-text. At f5ea00604, PUT /api/v1/automation/:name on the showcase's packaged flow answers 403 NOT_OVERRIDABLE with the shared sentence, and /meta gives the same verdict. That sentence names 'edit the source artifact and redeploy, or set OS_METADATA_WRITABLE'. ADR-0126 §2 (Regime C) says the in-place edit is 'refused loudly at the write door, the refusal naming the sanctioned path', and for a packaged flow the sanctioned path is clone (§7.1) and the enable/disable switch (§7.2). Neither is named. Seam: ADR-0126 §2 → runtime: packages/metadata-protocol/src/protocol.ts refusePackagedBaseOverride / refusePackagedBaseRemoval NOT_OVERRIDABLE sentences (relayed by packages/runtime/src/domains/automation.ts refusePackagedFlowBaseChange) · evidence: dogfood pin output at f5ea00604 · dedupe words: packaged flow refusal sentence sanctioned path clone NOT_OVERRIDABLE",
"carrier: none (承接者:无) · noted, not filed. On the showcase composition the /meta door is served by the REST server, and its refusal envelope is { error: string, code }. /automation (the dispatcher) answers { success, error: { code, message } }. So one refusal arrives in two envelopes. Pre-existing; recorded in the PR's Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed. @objectstack/rest declares @objectstack/metadata-protocol as a devDependency, and its built dist inlines a copy of ObjectStackProtocolImplementation. Pre-existing; recorded in the PR's Acceptance notes."
],
"gates": {
"node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0 · ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
"node scripts/check-adr-0087-registration.mjs --self-test": "exit 0 · ✓ check-adr-0087-registration --self-test: 441 assertions over real temp git repos (real scan()/assertInputs()",
"node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0 · · no `pull_request` payload was available to read a declaration from",
"node scripts/check-changeset-no-major.mjs --self-test": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/pars",
"node scripts/check-ci-filter-parity.mjs": "exit 0 · OK: all 187 declared cross-package glob(s) (134 unique) are covered by `core` or `crosspkg`, every `crosspkg` ",
"node scripts/check-closing-keyword-parity.mjs": "exit 0 · • packages/spec/CHANGELOG.md -- 7574210 bytes exceeds the sweep's 2097152-byte cutoff for UNREGISTERED files",
"node scripts/check-closing-keyword-parity.mjs --self-test": "exit 0 · ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to r",
"node scripts/check-comment-mask-adoption.mjs": "exit 0 · OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 record",
"node scripts/check-comment-mask-adoption.mjs --self-test": "exit 0 · PASS check-comment-mask-adoption --self-test (0 failure(s))",
"node scripts/check-comment-mask-corpus.mjs": "exit 0 · ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 7610 files, 0 disagree, 0 unparseable, 123.5s (comp",
"node scripts/check-empty-changeset.mjs --base origin/main": "exit 0 · ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-empty-changeset.mjs --self-test": "exit 0 · ✓ check-empty-changeset --self-test: 159 assertions over real temp git repos (real scan() path)",
"node scripts/check-issue-citations.mjs": "exit 0 · ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
"node scripts/check-keyed-text-bounds.mjs": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f",
"node scripts/check-keyed-text-bounds.mjs --self-test": "exit 0 · PASS check-keyed-text-bounds --self-test (0 failure(s))",
"node scripts/check-platform-object-tenancy-census.mjs": "exit 0 · ✓ platform-object tenancy census matches the tree: 82 platform-namespace objects, 56 in the machinery's reach,",
"node scripts/check-platform-object-tenancy-census.mjs --self-test": "exit 0 · ✓ check-platform-object-tenancy-census self-test: all checks pass (82 objects, 26 outside the machinery)",
"node scripts/check-plugin-teardown-shape.mjs": "exit 0 · ✓ check:plugin-teardown-shape: 67 Plugin implementation(s) across 7040 source(s) under packages/**; every tear",
"node scripts/check-plugin-teardown-shape.mjs --self-test": "exit 0 · ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and bo",
"node scripts/check-registry-log-declared.mjs": "exit 0 · examples/app-showcase — S1 constructs a SchemaRegistry in its tests",
"node scripts/check-registry-log-declared.mjs --self-test": "exit 0 · self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.",
"node scripts/check-rest-log-spy-declared.mjs": "exit 0 · OK: 30 of 233 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declar",
"node scripts/check-rest-log-spy-declared.mjs --self-test": "exit 0 · check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).",
"node scripts/check-system-context-census.mjs": "exit 0 · check-system-context-census: OK — 106 elevation read sites in 19 packages across 45 files, living in 89 symbol",
"node scripts/check-system-context-census.mjs --self-test": "exit 0 · check-system-context-census --self-test: all cases passed",
"node scripts/check-undeclared-dep-imports.mjs": "exit 0 · ⚠ The delta is information, not a verdict — the floors are `›=` and cannot see an upward drift at all, which i",
"node scripts/check-undeclared-dep-imports.mjs --self-test": "exit 0 · PASS check-undeclared-dep-imports --self-test (0 failure(s))",
"node scripts/docs-audit/check-affected-docs.mjs": "exit 0 · → the unreachable rows themselves: this command with --json",
"node scripts/docs-audit/check-drift-comment.mjs": "exit 0 · ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
"node scripts/pm/release-rehearsal-clone.mjs --self-test": "exit 0 · ✓ self-test passed",
"pnpm --filter @objectstack/spec run check:duration-unit-keys": "exit 0 · ✓ check:duration-unit-keys — 199 unit-declaring numeric key(s) across 2748 source file(s) all carry their unit",
"pnpm --filter @objectstack/spec run check:empty-state": "exit 0 · ✓ all classified (2 closed, 2 open, 4 output, 9 scope)",
"pnpm --filter @objectstack/spec run check:liveness": "exit 0 · (not a completeness claim about the 531 child key(s) under the declared blanket verdicts above — those are rec",
"pnpm --filter @objectstack/spec run check:strictness-ledger": "exit 0 · untriaged: 1200 object site(s) across 9 director(ies)",
"pnpm --filter @objectstack/spec run check:variant-docs": "exit 0 · ✓ variant/doc gate: 18 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 10 exempt",
"pnpm check:changeset-gate-self-tests": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/pars",
"pnpm check:cross-package-test-inputs": "exit 0 · OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them ",
"pnpm check:dispatcher-error-vocabulary": "exit 0 · [#15723] the ARGUMENT POSITION of new APIError( … ) and APIError.from( … ) IS now in this gate's population — ",
"pnpm check:doc-authoring": "exit 0 · ✓ doc authoring guard: sibling-package prose ids hold the baseline — 794 pinned site(s) across 227 file(s), 92",
"pnpm check:driver-memory-census": "exit 0 · check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled fi",
"pnpm check:dts-closure": "exit 0 · check-dts-closure: 71 built package(s) swept - 167/167 declared declaration file(s) present across 71 package(",
"pnpm check:dual-build-cjs-loads": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f",
"pnpm check:durability-log-level": "exit 0 · ✓ read-seam invention (#5186 + #6451 + #9165, 3 package roots, vocabulary find/findOne/count): 68 read seam(s)",
"pnpm check:engine-double-contract": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f",
"pnpm check:filter-alias-parity": "exit 0 · check:filter-alias-parity: OK (4 transport spelling(s) of the `where` slot, identical on both sides: $filter, ",
"pnpm check:gitlink-declared": "exit 0 · check-gitlink-declared: OK (9424 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so",
"pnpm check:issue-citations": "exit 0 · ✅ check-issue-citations --self-test: grammar narrowed, qualifier a closed set of repositories, four 404 causes",
"pnpm check:lean-entry-closure": "exit 0 · Admitted set held exactly (15 packages); 6 denied names absent.",
"pnpm check:logger-receiver-detach": "exit 0 · control corpus fired on all five detach shapes in this same run, and stayed silent on the measured `console` a",
"pnpm check:nul-bytes": "exit 0 · check-nul-bytes: OK (scanned 9417 text file(s) -- 9417 tracked, 0 untracked-not-ignored; skipped 7 binary; no ",
"pnpm check:objectql-double-limit": "exit 0 · baseline key set verified against 8acdae9: no files added.",
"pnpm check:objectui-changeset": "exit 0 · ✓ objectui-range --self-test: all checks passed",
"pnpm check:org-identifier": "exit 0 · check-org-identifier: OK (3044 author-facing source file(s), 17 session binding(s) resolved, no removed sessio",
"pnpm check:page-declaration-shape": "exit 0 · blind spot: 1 computed carrier(s) no source scan can enumerate — examples/app-crm/objectstack.config.ts:86.",
"pnpm check:pm-changeset-deadline-census": "exit 0 · ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, t",
"pnpm check:published-files": "exit 0 · ✓ check:published-files — 69 publishable package(s) of 80 workspace member(s) declare a `files` whitelist that",
"pnpm check:query-options-erasure": "exit 0 · baseline key set verified against 8acdae9: no files added.",
"pnpm check:refd-timer-probe": "exit 0 · 1 code site(s), all inside the approved module, which is present and still reads it.",
"pnpm check:route-envelope": "exit 0 · read/write discriminator: 11 file(s) skipped as fetch readers (77 zero-argument `res.json()` call(s), none swe",
"pnpm check:slot-lookup": "exit 0 · baseline key set verified against 8acdae9: no files added.",
"pnpm check:sourcemap-no-sources-content": "exit 0 · check-sourcemap-no-sources-content: 68 built package(s) swept - 518 map(s), none embed source text.",
"pnpm check:test-source-alias": "exit 0 · check-test-source-alias OK — 73 packages with tests scanned; 60 registered as still resolving a workspace dep ",
"pnpm check:tier-file-adoption": "exit 0 · @objectstack/cli — 75 file(s); imports readTierMode, selectTierFiles from scripts/nightly-tiers.mjs (via packa",
"pnpm check:type-check-coverage": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f",
"pnpm check:type-check-debt": "exit 0 · surplus: none — every entry sits exactly at its measurement, so any new error is red.",
"pnpm check:watch-hint-literal": "exit 0 · ✓ check-watch-hint-literal: 71 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 47, ROOT_FILE_",
"pnpm check:where-matcher": "exit 0 · baseline key set verified against 8acdae9: no files added.",
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list": "exit 0 · ✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero — all 67 recorded an exit code and none of them is 3). Derived at f5ea00604, merge base 8acdae9d8.",
"pnpm check:error-code-casing (roster, error-code family)": "exit 0",
"pnpm --filter @objectstack/spec check:error-code-provenance (roster, error-code family)": "exit 0 · OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver (330 sites: 313 listed, 17 waived)",
"pnpm lint (narrowed, proven)": "eslint --no-inline-config --format json on the 6 changed .ts files at f5ea00604: 6 files linted, 0 errors, 0 warnings, exit 0. Population: eslint.config.mjs file globs (**/*.{ts,...} and packages/**/*.{ts,...}) cover all 6. Invariance: the config enables no type-aware linting (no parserOptions.project, no typed rules, stated in eslint.config.mjs) and its 4 plugins are local AST rules, so the diff cannot move an untouched file's verdict.",
"CI on PR #20817 at f5ea00604 (read 08:30Z)": "in_progress: 32 check runs, 12 success, 3 skipped, 16 in_progress, 1 queued, 0 failed"
},
"deviations": [
"Surface: the claim's stop-line ('stop if a new export is needed') conflicted with the dispatch ('fix producer-side outside packages/spec'). I followed the dispatch. The seat adopted the metadata-protocol method and its pins onto the claim and re-declared Clause-② yes (widening), which is copied as PR body line 2. The changeset stays metadata-protocol minor / runtime patch, with no BREAKING banner.",
"Surface: the POST / arm was added as a bounded in-place fix at the seat's instruction, after I reported the measured create-overwrite. The claim was updated in the same round.",
"Surface: per dispatch mechanism-assumption 5, there is a real-HTTP dogfood pin in packages/qa/dogfood/test/packaged-flow-write-door-parity.dogfood.test.ts. The fold-population pin packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts gained the new verb, because the derived population pins its size.",
"PM mechanism assumptions: #1 confirmed. #2: the real predicate was private to the protocol class, not the spec helpers or the permission-set lock; reusing it needed a metadata-protocol public method but no spec export and no new code. #3 confirmed; the ordering is documented. #4: all pinned. #5: the dispatcher mounts the route (confirmed), and the dogfood pin exercises it over HTTP.",
"The protocol pin resolves the verdict by the flow name. Asked mid-task by the seat: the verdict reads SchemaRegistry.getArtifactItem (packages/objectql/src/registry.ts:3919), the loader-registered PACKAGE_ID:NAME entries, filtered by isCodeArtifactBody on that registry entry. It never reads the request body or the engine's registered flow, and a pin proves body stamps decide nothing both ways.",
"Merged origin/main 4 times (the last merge base is 8acdae9d8, which includes #20726). Not merged: the 2 later commits (#20779 clone mount on the dispatcher bridge, #20788 docs); they touch neither changed file.",
"Tooling slip: one early metadata-protocol run used 'pnpm test -- --maxWorkers=2'. vitest dropped the flag, which ran the intended full suite with default workers. Later runs used exec vitest run. Two long foreground commands were auto-backgrounded by the harness at the 600s cap; I waited in the foreground with tail --pid and read the logs.",
"check-changeset-no-major ran locally with no pull_request payload, so the Clause-② level axis was not read locally ('no pull_request payload was available'). CI reads the PR body; metadata-protocol is graded minor.",
"CI is not awaited: it was in_progress at report time (see gates)."
],
"files_changed": [
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/automation-packaged-base-lock.test.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts",
"packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts",
"packages/qa/dogfood/test/packaged-flow-write-door-parity.dogfood.test.ts",
".changeset/20679-automation-door-package-lock.md"
]
}
{
"issue": 20679,
"status": "done",
"branch": "claude/issue-20679-automation-door-package-lock",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20817",
"session": "session_01VvcEokUG1tvVxkceYfR5XB — the dispatching PM session's id (subagent run; same id as the parent, per the dispatch).",
"premise_still_valid": true,
"summary": "Patch round 1: the contract review's changeset wording finding. New head e4006d685 (e4006d68594d433159b1a0e8addd9585e5929f59), one commit on top of the reviewed f5ea00604. In .changeset/20679-automation-door-package-lock.md, one sentence changed. Before: 'All three now answer `403` `NOT_OVERRIDABLE` for a packaged flow, with the same message the metadata door gives.' After: 'All three now answer a packaged flow with the same code and status the metadata door gives (`403` `NOT_OVERRIDABLE`), and with the same sentence wherever the metadata protocol's own package door answers.' The new wording is true on both topologies: on a host-config kernel /meta's refusal comes from SysMetadataRepository.assertAllowed with the repository's own sentence. The rest of the changeset is byte-identical (git diff: 1 insertion, 1 deletion, that line only), including the line-initial 'Clause-②: yes (widening)'. No code or test file touched, no PR body edit, no merge of main (PR reads mergeable: true after the push). The worktree was recreated on the existing branch after a fetch, with no new branch.",
"tests": "No code or test changed in this round, so no test was re-run. The code and tests are unchanged from f5ea00604, the head the contract review passed; their readings are in the previous os-dev-report (comment 5907350558).",
"mcp_calls": "0 — no MCP GitHub tool used. Reads were REST GETs (PR state after the push).",
"api_writes": "1 — this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/20679/comments, through the fleet-write relay as objectstack-fleet[bot]. git push is not counted (it is not a REST write).",
"open_questions": [],
"out_of_scope_findings": [],
"gates": {
"node scripts/check-empty-changeset.mjs --base origin/main": "exit 0 · ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0 · ✓ This diff introduces no `major` bump. · no `pull_request` payload was available to read a declaration from (level axis read by CI from the PR body)",
"node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0 · ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
"pnpm check:doc-authoring": "exit 0 · ✓ doc authoring guard: sibling-package prose ids hold the baseline — 794 pinned site(s) across 227 file(s), 92127 string(s) read in 1260 parsed source(s), no growth, no burn-down unrecorded."
},
"deviations": [
"None in this round. Scope was exactly the seat's instruction: one changeset phrase. The four named gates were run on the committed head before the push; every exit code is recorded in gates."
],
"files_changed": [
".changeset/20679-automation-door-package-lock.md"
]
}
Contract review of record:5908296340 on the PR, CONTRACT_REVIEW_TIER, head e4006d68, PASS.
It is a delta re-review over the review of f5ea0060, whose one wording finding in the changeset was fixed in patch round 1 (one line).
The seat verified the load-bearing claim itself: diff -w of both lifted package doors against the merge-base reads 0 changed lines, and the code blobs are identical at both heads.
Checklist:
Draft, base main, first line Fixes #20679, second line Clause-②: yes (widening), and no other closing keyword (scanned).
7 files, +1012 / −93, all on the claim's surface as amended in place three times. The amendments cover the cross-lane metadata-protocol method (with a notice to domain:engine, 5905804651), the POST / bounded in-place fix, and the two test-only pins.
35 check runs on the head: 32 success and 3 skipped. mergeable_state reads clean.
Checklist item access-security.packaged-flow-write-door-parity: clauses 1–4 pass over the booted showcase in the dogfood pin. Its "EXPECTED FAIL today" notes go stale on landing, a checklist-author follow-up. Noted, not filed.
Two envelopes for one refusal: pre-existing. Acceptance notes, noted, not filed.
@objectstack/rest inlines the protocol class: a pre-existing packaging shape with no measured defect. Acceptance notes, dropped as a card: it is outside the three classes.
Landing reading:4b45afaed5e2a1afdbc8c4fd36005dce210baa4e is on origin/main as a single-parent squash over 73155fedc. Content: git diff --stat of the PR head e4006d68 against 4b45afae over the PR's 7 files is empty. packages/runtime/src/domains/automation.ts on origin/main names refusePackagedFlowBaseChange on 5 lines, against 0 at the parent. packages/metadata-protocol/src/protocol.ts names packagedBaseRefusal on 5 lines.
Activity
objectstack-fleet commentedon Sep 29, 2026
Triage: first grade —
bug·security·priority:p1·domain:cli·area:access·pm:queue. Direction: the write door goes through the same locked-base check the metadata door appliesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T17:53Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the write door is served by
packages/runtime⇒domain:cli, by the lane table. The engine isdomain:services's, and it is consulted, not edited. The reproduction stays withheld, as the runner filed it, and this grade adds no request detail.What the public checklist item already states.
access-security.packaged-flow-write-door-parity(docs/qa/platform-checklist/areas/access-security.json) describes the gap at the level of doors:manage_metadata.Why p1, not p0.
main, so by the security rule it is at least p1.Direction.
objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 1 of the
domain:cliseat's sessionsession_01VvcEokUG1tvVxkceYfR5XB(batch3):priority:p1·security, dispatched first at the maintainer's direction in this session's chat (「20679 20676 优先」)Session:
session_01VvcEokUG1tvVxkceYfR5XBAccount:
huangyiireneBranch:
claude/issue-20679-automation-door-package-lockWorktree:
objectstack-issue-20679Domain:
domain:cliSeat:
domain:cli#1File surface:
packages/runtime/src/domains/automation.ts: thePUT /:nameandDELETE /:namearms only (the definition-write and removal doors), plus a new pin file beside it inpackages/runtime/src/domains/.registerFlow/unregisterFlow(triage5895656793: boot registers packaged flows through it)..changeset/20679-*.mdfor@objectstack/runtime.POST /create arm ofpackages/runtime/src/domains/automation.tsgets the samerefusePackagedFlowBaseChangecall after its name check, plus one pin. Measured by the dev at3690c44b: a create onto a packaged flow's name answers200and overwrites the flow in the engine. The fix is the same defect class (the ADR-0126 §2 locked base at a write door) with the same mechanical fix. The file has no other claim holder: automation toggle door: switching a customer-authored flow off or on answers 400 VALIDATION_FAILED 'Package is required' — the activation ledger requires package_id and toggleFlow writes an empty one #20726 has landed (PR fix(service-automation)!: the toggle door switches packaged flows only; a customer flow is refused, naming its status switch (#20726) #20780), and automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761 ispm:blockedon this card. The gate family is the same. The maintainer's automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761 ruling (5904938166, rule 2) names every automation write door, create included, as reusing this check.packages/qa/dogfood/test/packaged-flow-write-door-parity.dogfood.test.ts(the real-HTTP pin the dispatch order asked for, driving checklist clauses 1–4 over a booted showcase) andpackages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts(its derived population pins the protocol's verb count, which the newpackagedBaseRefusalmoves). Test files only; no other product file.packages/metadata-protocol/src/protocol.ts: one public method,packagedBaseRefusal, onObjectStackProtocolImplementation. It exposes the/metadoor's own locked-base verdict. The private helpers it calls are lifted out unchanged, not copied. Its pins sit beside it, and@objectstack/metadata-protocolgets aminorchangeset. The dispatch order told the dev to fix on the producer side; this claim's stop-line said to stop on a new export. The two texts disagreed, and that was the seat's error. The dev followed the order, and the seat adopts the surface here, with a cross-lane notice todomain:engine([PM seat] domain:engine — 🟢 os-project-manager #6367).runtimewithout a new export, the dev stops and reports the producer side, ⛔ it does not copy the predicate.Stop on breach and explain in the report
Container & model:
M,mode:subagent,model: default tier (opus)(dispatch-gates --tierat0d9349fe: no path-derived mandate; floor sonnet · default opus · ceiling fable). Security-boundary card, so it gets the judgment tier.Clause-②: yes (widening)
Clause-② reading (re-read by the seat, amended in place): the refusal itself is a runtime authorization and integrity refusal, the negative boundary of clause ②. But the delivered diff adds one public method,
packagedBaseRefusal, to the publishedObjectStackProtocolImplementation(@objectstack/metadata-protocol). An added export member widens the public surface (references/lanes/spec.md: 「放宽接受集或扩大公开面的卡,不论多小,即条款②」), so the line isyes (widening):minorfor@objectstack/metadata-protocol. No new error code; nopackages/specedit. The at-tier contract review is owed before enqueue.Thread-read: 5895656793
Serial constraints cleared:
#20726 (domain:services, claim 5903568667) holds the toggle route's DESCRIPTION region of packages/runtime/src/domains/automation.ts. That region is disjoint from the PUT and DELETE arms; only .objectui-sha is a single-claim path, so the later lander resolves any textual conflict. #20725 landed at 0d9349fe (PR #20759): it changed the engine's unregisterFlow (DELETE_RESTRICTED for a reachable packaged subflow), and this dispatch bases on it. None of the 10 open PRs touches automation.ts (file lists read in this act). #20676 (this seat, same round) edits dispatcher-plugin.ts and route-ledger.ts, disjoint files.Taken 2026-09-30T03:49Z,
main0d9349fe. The reproduction stays withheld as filed. The dev works from the public checklist itemaccess-security.packaged-flow-write-door-parity(docs/qa/platform-checklist/areas/access-security.json) and the triage direction.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{ "issue": 20679, "status": "done", "branch": "claude/issue-20679-automation-door-package-lock", "pr": "https://github.com/objectstack-ai/objectstack/pull/20817", "session": "session_01VvcEokUG1tvVxkceYfR5XB — the dispatching PM session's id (subagent run; same id as the parent, per the dispatch).", "premise_still_valid": true, "summary": "Verified on origin/main before editing: PUT and DELETE /automation/:name ran only the manage_metadata gate and went straight to registerFlow / unregisterFlow. The /meta door's locked-base verdict for a flow was private to ObjectStackProtocolImplementation: isArtifactBacked plus isOverlayAllowed. It is enforced in the protocol on environment kernels and by SysMetadataRepository.assertAllowed at the write on host-config kernels. It is not spec's evaluateLockFor* (that is the ADR-0010 per-item _lock) and not the permission-set lock. Fix: packages/metadata-protocol gains one public method, packagedBaseRefusal({ type, name, operation }). The seat adopted it as a cross-lane surface and re-declared the claim as Clause-② yes (widening). saveMetaItem's and deleteMetaItem's inline package doors are lifted verbatim into two private helpers that both methods still call; diff -w on the lifted bodies reads 0 changed lines (80/80 and 12/12). packages/runtime automation.ts asks that verdict with the flow NAME only, before the engine is called, and relays the refusal (403 NOT_OVERRIDABLE, the producer's ledgered code and sentence). It does this at PUT /:name, at DELETE /:name, and, as the seat-adopted bounded in-place fix, at POST / onto an existing name. POST / onto a packaged name measured 200 with an overwrite at 3690c44b before the fix. The lock sits ahead of the engine's DELETE_RESTRICTED / 409. Customer flows, clone, toggle and the OS_METADATA_WRITABLE hatch stay open. A dogfood pin over the booted showcase shows checklist clauses 1-4 passing.", "tests": "Final head f5ea00604: every runtime automation-*.test.ts 25 files / 478 passed; runtime typecheck exit 0 (check:test-typecheck OK); protocol pins 27 passed; dogfood pin 5 passed after a runtime rebuild. At 3690c44b9 (merge over #20726): runtime local 292 files / 4215 passed, 1 skipped; runtime repo 727 passed; metadata-protocol 191 files passed, 3 skipped / 2801 passed, 19 skipped; typecheck exit 0; objectql, the 20 files pinning NOT_OVERRIDABLE / NOT_CREATABLE / ITEM_LOCKED, 362 passed; rest 3 files 41 passed. Ablations (commit first; scripts/ablation-replace.mjs with anchor hit and blob change; ablation-dist-preflight with the marker in dist for rebuilt legs; every restore proven by blob == HEAD, git diff HEAD empty, tree clean, marker absent): door helper disabled: runtime pin 7 red / 7 green, dogfood 3 red / 2 green; packagedBaseRefusal returns null (metadata-protocol rebuilt): protocol pin 6 red / 3 green, runtime pin 6 red / 8 green; re-raise discriminator widened: protocol pin 1 red / 8 green; POST call removed at f5ea00604: runtime pin 1 red / 14 green. All predicted before running; all pins green again after each restore.", "mcp_calls": "0 — no MCP GitHub tool used. Reads were REST GETs (issue, comments, pulls, check-runs); two SendMessage replies went to the coordinator, which are not GitHub calls.", "api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot], one repository_dispatch each: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20817, body read back byte-identical, 11499 bytes); (2) label-write assign → POST /repos/objectstack-ai/objectstack/issues/20817/assignees (huangyiirene, read back MATCHES); (3) this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/20679/comments. git push is not counted (it is not a REST write).", "open_questions": [], "out_of_scope_findings": [ "class: b · reach: public door + exception: release-text. At f5ea00604, PUT /api/v1/automation/:name on the showcase's packaged flow answers 403 NOT_OVERRIDABLE with the shared sentence, and /meta gives the same verdict. That sentence names 'edit the source artifact and redeploy, or set OS_METADATA_WRITABLE'. ADR-0126 §2 (Regime C) says the in-place edit is 'refused loudly at the write door, the refusal naming the sanctioned path', and for a packaged flow the sanctioned path is clone (§7.1) and the enable/disable switch (§7.2). Neither is named. Seam: ADR-0126 §2 → runtime: packages/metadata-protocol/src/protocol.ts refusePackagedBaseOverride / refusePackagedBaseRemoval NOT_OVERRIDABLE sentences (relayed by packages/runtime/src/domains/automation.ts refusePackagedFlowBaseChange) · evidence: dogfood pin output at f5ea00604 · dedupe words: packaged flow refusal sentence sanctioned path clone NOT_OVERRIDABLE", "carrier: none (承接者:无) · noted, not filed. On the showcase composition the /meta door is served by the REST server, and its refusal envelope is { error: string, code }. /automation (the dispatcher) answers { success, error: { code, message } }. So one refusal arrives in two envelopes. Pre-existing; recorded in the PR's Acceptance notes.", "carrier: none (承接者:无) · noted, not filed. @objectstack/rest declares @objectstack/metadata-protocol as a devDependency, and its built dist inlines a copy of ObjectStackProtocolImplementation. Pre-existing; recorded in the PR's Acceptance notes." ], "gates": { "node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0 · ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).", "node scripts/check-adr-0087-registration.mjs --self-test": "exit 0 · ✓ check-adr-0087-registration --self-test: 441 assertions over real temp git repos (real scan()/assertInputs()", "node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0 · · no `pull_request` payload was available to read a declaration from", "node scripts/check-changeset-no-major.mjs --self-test": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/pars", "node scripts/check-ci-filter-parity.mjs": "exit 0 · OK: all 187 declared cross-package glob(s) (134 unique) are covered by `core` or `crosspkg`, every `crosspkg` ", "node scripts/check-closing-keyword-parity.mjs": "exit 0 · • packages/spec/CHANGELOG.md -- 7574210 bytes exceeds the sweep's 2097152-byte cutoff for UNREGISTERED files", "node scripts/check-closing-keyword-parity.mjs --self-test": "exit 0 · ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to r", "node scripts/check-comment-mask-adoption.mjs": "exit 0 · OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 record", "node scripts/check-comment-mask-adoption.mjs --self-test": "exit 0 · PASS check-comment-mask-adoption --self-test (0 failure(s))", "node scripts/check-comment-mask-corpus.mjs": "exit 0 · ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 7610 files, 0 disagree, 0 unparseable, 123.5s (comp", "node scripts/check-empty-changeset.mjs --base origin/main": "exit 0 · ✓ No changeset from the merge base modified or deleted by this diff (#17712).", "node scripts/check-empty-changeset.mjs --self-test": "exit 0 · ✓ check-empty-changeset --self-test: 159 assertions over real temp git repos (real scan() path)", "node scripts/check-issue-citations.mjs": "exit 0 · ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).", "node scripts/check-keyed-text-bounds.mjs": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f", "node scripts/check-keyed-text-bounds.mjs --self-test": "exit 0 · PASS check-keyed-text-bounds --self-test (0 failure(s))", "node scripts/check-platform-object-tenancy-census.mjs": "exit 0 · ✓ platform-object tenancy census matches the tree: 82 platform-namespace objects, 56 in the machinery's reach,", "node scripts/check-platform-object-tenancy-census.mjs --self-test": "exit 0 · ✓ check-platform-object-tenancy-census self-test: all checks pass (82 objects, 26 outside the machinery)", "node scripts/check-plugin-teardown-shape.mjs": "exit 0 · ✓ check:plugin-teardown-shape: 67 Plugin implementation(s) across 7040 source(s) under packages/**; every tear", "node scripts/check-plugin-teardown-shape.mjs --self-test": "exit 0 · ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and bo", "node scripts/check-registry-log-declared.mjs": "exit 0 · examples/app-showcase — S1 constructs a SchemaRegistry in its tests", "node scripts/check-registry-log-declared.mjs --self-test": "exit 0 · self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.", "node scripts/check-rest-log-spy-declared.mjs": "exit 0 · OK: 30 of 233 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declar", "node scripts/check-rest-log-spy-declared.mjs --self-test": "exit 0 · check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).", "node scripts/check-system-context-census.mjs": "exit 0 · check-system-context-census: OK — 106 elevation read sites in 19 packages across 45 files, living in 89 symbol", "node scripts/check-system-context-census.mjs --self-test": "exit 0 · check-system-context-census --self-test: all cases passed", "node scripts/check-undeclared-dep-imports.mjs": "exit 0 · ⚠ The delta is information, not a verdict — the floors are `›=` and cannot see an upward drift at all, which i", "node scripts/check-undeclared-dep-imports.mjs --self-test": "exit 0 · PASS check-undeclared-dep-imports --self-test (0 failure(s))", "node scripts/docs-audit/check-affected-docs.mjs": "exit 0 · → the unreachable rows themselves: this command with --json", "node scripts/docs-audit/check-drift-comment.mjs": "exit 0 · ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).", "node scripts/pm/release-rehearsal-clone.mjs --self-test": "exit 0 · ✓ self-test passed", "pnpm --filter @objectstack/spec run check:duration-unit-keys": "exit 0 · ✓ check:duration-unit-keys — 199 unit-declaring numeric key(s) across 2748 source file(s) all carry their unit", "pnpm --filter @objectstack/spec run check:empty-state": "exit 0 · ✓ all classified (2 closed, 2 open, 4 output, 9 scope)", "pnpm --filter @objectstack/spec run check:liveness": "exit 0 · (not a completeness claim about the 531 child key(s) under the declared blanket verdicts above — those are rec", "pnpm --filter @objectstack/spec run check:strictness-ledger": "exit 0 · untriaged: 1200 object site(s) across 9 director(ies)", "pnpm --filter @objectstack/spec run check:variant-docs": "exit 0 · ✓ variant/doc gate: 18 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 10 exempt", "pnpm check:changeset-gate-self-tests": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/pars", "pnpm check:cross-package-test-inputs": "exit 0 · OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them ", "pnpm check:dispatcher-error-vocabulary": "exit 0 · [#15723] the ARGUMENT POSITION of new APIError( … ) and APIError.from( … ) IS now in this gate's population — ", "pnpm check:doc-authoring": "exit 0 · ✓ doc authoring guard: sibling-package prose ids hold the baseline — 794 pinned site(s) across 227 file(s), 92", "pnpm check:driver-memory-census": "exit 0 · check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled fi", "pnpm check:dts-closure": "exit 0 · check-dts-closure: 71 built package(s) swept - 167/167 declared declaration file(s) present across 71 package(", "pnpm check:dual-build-cjs-loads": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f", "pnpm check:durability-log-level": "exit 0 · ✓ read-seam invention (#5186 + #6451 + #9165, 3 package roots, vocabulary find/findOne/count): 68 read seam(s)", "pnpm check:engine-double-contract": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f", "pnpm check:filter-alias-parity": "exit 0 · check:filter-alias-parity: OK (4 transport spelling(s) of the `where` slot, identical on both sides: $filter, ", "pnpm check:gitlink-declared": "exit 0 · check-gitlink-declared: OK (9424 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so", "pnpm check:issue-citations": "exit 0 · ✅ check-issue-citations --self-test: grammar narrowed, qualifier a closed set of repositories, four 404 causes", "pnpm check:lean-entry-closure": "exit 0 · Admitted set held exactly (15 packages); 6 denied names absent.", "pnpm check:logger-receiver-detach": "exit 0 · control corpus fired on all five detach shapes in this same run, and stayed silent on the measured `console` a", "pnpm check:nul-bytes": "exit 0 · check-nul-bytes: OK (scanned 9417 text file(s) -- 9417 tracked, 0 untracked-not-ignored; skipped 7 binary; no ", "pnpm check:objectql-double-limit": "exit 0 · baseline key set verified against 8acdae9: no files added.", "pnpm check:objectui-changeset": "exit 0 · ✓ objectui-range --self-test: all checks passed", "pnpm check:org-identifier": "exit 0 · check-org-identifier: OK (3044 author-facing source file(s), 17 session binding(s) resolved, no removed sessio", "pnpm check:page-declaration-shape": "exit 0 · blind spot: 1 computed carrier(s) no source scan can enumerate — examples/app-crm/objectstack.config.ts:86.", "pnpm check:pm-changeset-deadline-census": "exit 0 · ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, t", "pnpm check:published-files": "exit 0 · ✓ check:published-files — 69 publishable package(s) of 80 workspace member(s) declare a `files` whitelist that", "pnpm check:query-options-erasure": "exit 0 · baseline key set verified against 8acdae9: no files added.", "pnpm check:refd-timer-probe": "exit 0 · 1 code site(s), all inside the approved module, which is present and still reads it.", "pnpm check:route-envelope": "exit 0 · read/write discriminator: 11 file(s) skipped as fetch readers (77 zero-argument `res.json()` call(s), none swe", "pnpm check:slot-lookup": "exit 0 · baseline key set verified against 8acdae9: no files added.", "pnpm check:sourcemap-no-sources-content": "exit 0 · check-sourcemap-no-sources-content: 68 built package(s) swept - 518 map(s), none embed source text.", "pnpm check:test-source-alias": "exit 0 · check-test-source-alias OK — 73 packages with tests scanned; 60 registered as still resolving a workspace dep ", "pnpm check:tier-file-adoption": "exit 0 · @objectstack/cli — 75 file(s); imports readTierMode, selectTierFiles from scripts/nightly-tiers.mjs (via packa", "pnpm check:type-check-coverage": "exit 0 · ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the f", "pnpm check:type-check-debt": "exit 0 · surplus: none — every entry sits exactly at its measurement, so any new error is red.", "pnpm check:watch-hint-literal": "exit 0 · ✓ check-watch-hint-literal: 71 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 47, ROOT_FILE_", "pnpm check:where-matcher": "exit 0 · baseline key set verified against 8acdae9: no files added.", "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list": "exit 0 · ✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero — all 67 recorded an exit code and none of them is 3). Derived at f5ea00604, merge base 8acdae9d8.", "pnpm check:error-code-casing (roster, error-code family)": "exit 0", "pnpm --filter @objectstack/spec check:error-code-provenance (roster, error-code family)": "exit 0 · OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver (330 sites: 313 listed, 17 waived)", "pnpm lint (narrowed, proven)": "eslint --no-inline-config --format json on the 6 changed .ts files at f5ea00604: 6 files linted, 0 errors, 0 warnings, exit 0. Population: eslint.config.mjs file globs (**/*.{ts,...} and packages/**/*.{ts,...}) cover all 6. Invariance: the config enables no type-aware linting (no parserOptions.project, no typed rules, stated in eslint.config.mjs) and its 4 plugins are local AST rules, so the diff cannot move an untouched file's verdict.", "CI on PR #20817 at f5ea00604 (read 08:30Z)": "in_progress: 32 check runs, 12 success, 3 skipped, 16 in_progress, 1 queued, 0 failed" }, "deviations": [ "Surface: the claim's stop-line ('stop if a new export is needed') conflicted with the dispatch ('fix producer-side outside packages/spec'). I followed the dispatch. The seat adopted the metadata-protocol method and its pins onto the claim and re-declared Clause-② yes (widening), which is copied as PR body line 2. The changeset stays metadata-protocol minor / runtime patch, with no BREAKING banner.", "Surface: the POST / arm was added as a bounded in-place fix at the seat's instruction, after I reported the measured create-overwrite. The claim was updated in the same round.", "Surface: per dispatch mechanism-assumption 5, there is a real-HTTP dogfood pin in packages/qa/dogfood/test/packaged-flow-write-door-parity.dogfood.test.ts. The fold-population pin packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts gained the new verb, because the derived population pins its size.", "PM mechanism assumptions: #1 confirmed. #2: the real predicate was private to the protocol class, not the spec helpers or the permission-set lock; reusing it needed a metadata-protocol public method but no spec export and no new code. #3 confirmed; the ordering is documented. #4: all pinned. #5: the dispatcher mounts the route (confirmed), and the dogfood pin exercises it over HTTP.", "The protocol pin resolves the verdict by the flow name. Asked mid-task by the seat: the verdict reads SchemaRegistry.getArtifactItem (packages/objectql/src/registry.ts:3919), the loader-registered PACKAGE_ID:NAME entries, filtered by isCodeArtifactBody on that registry entry. It never reads the request body or the engine's registered flow, and a pin proves body stamps decide nothing both ways.", "Merged origin/main 4 times (the last merge base is 8acdae9d8, which includes #20726). Not merged: the 2 later commits (#20779 clone mount on the dispatcher bridge, #20788 docs); they touch neither changed file.", "Tooling slip: one early metadata-protocol run used 'pnpm test -- --maxWorkers=2'. vitest dropped the flag, which ran the intended full suite with default workers. Later runs used exec vitest run. Two long foreground commands were auto-backgrounded by the harness at the 600s cap; I waited in the foreground with tail --pid and read the logs.", "check-changeset-no-major ran locally with no pull_request payload, so the Clause-② level axis was not read locally ('no pull_request payload was available'). CI reads the PR body; metadata-protocol is graded minor.", "CI is not awaited: it was in_progress at report time (see gates)." ], "files_changed": [ "packages/runtime/src/domains/automation.ts", "packages/runtime/src/domains/automation-packaged-base-lock.test.ts", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts", "packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts", "packages/qa/dogfood/test/packaged-flow-write-door-parity.dogfood.test.ts", ".changeset/20679-automation-door-package-lock.md" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{ "issue": 20679, "status": "done", "branch": "claude/issue-20679-automation-door-package-lock", "pr": "https://github.com/objectstack-ai/objectstack/pull/20817", "session": "session_01VvcEokUG1tvVxkceYfR5XB — the dispatching PM session's id (subagent run; same id as the parent, per the dispatch).", "premise_still_valid": true, "summary": "Patch round 1: the contract review's changeset wording finding. New head e4006d685 (e4006d68594d433159b1a0e8addd9585e5929f59), one commit on top of the reviewed f5ea00604. In .changeset/20679-automation-door-package-lock.md, one sentence changed. Before: 'All three now answer `403` `NOT_OVERRIDABLE` for a packaged flow, with the same message the metadata door gives.' After: 'All three now answer a packaged flow with the same code and status the metadata door gives (`403` `NOT_OVERRIDABLE`), and with the same sentence wherever the metadata protocol's own package door answers.' The new wording is true on both topologies: on a host-config kernel /meta's refusal comes from SysMetadataRepository.assertAllowed with the repository's own sentence. The rest of the changeset is byte-identical (git diff: 1 insertion, 1 deletion, that line only), including the line-initial 'Clause-②: yes (widening)'. No code or test file touched, no PR body edit, no merge of main (PR reads mergeable: true after the push). The worktree was recreated on the existing branch after a fetch, with no new branch.", "tests": "No code or test changed in this round, so no test was re-run. The code and tests are unchanged from f5ea00604, the head the contract review passed; their readings are in the previous os-dev-report (comment 5907350558).", "mcp_calls": "0 — no MCP GitHub tool used. Reads were REST GETs (PR state after the push).", "api_writes": "1 — this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/20679/comments, through the fleet-write relay as objectstack-fleet[bot]. git push is not counted (it is not a REST write).", "open_questions": [], "out_of_scope_findings": [], "gates": { "node scripts/check-empty-changeset.mjs --base origin/main": "exit 0 · ✓ No changeset from the merge base modified or deleted by this diff (#17712).", "node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0 · ✓ This diff introduces no `major` bump. · no `pull_request` payload was available to read a declaration from (level axis read by CI from the PR body)", "node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0 · ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).", "pnpm check:doc-authoring": "exit 0 · ✓ doc authoring guard: sibling-package prose ids hold the baseline — 794 pinned site(s) across 227 file(s), 92127 string(s) read in 1260 parsed source(s), no growth, no burn-down unrecorded." }, "deviations": [ "None in this round. Scope was exactly the seat's instruction: one changeset phrase. The four named gates were run on the committed head before the push; every exit code is recorded in gates." ], "files_changed": [ ".changeset/20679-automation-door-package-lock.md" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
ACCEPT: PR #20817 at
e4006d68(the packaged-flow lock on the automation definition doors)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T09:28Z5908296340on the PR,CONTRACT_REVIEW_TIER, heade4006d68, PASS.f5ea0060, whose one wording finding in the changeset was fixed in patch round 1 (one line).diff -wof both lifted package doors against the merge-base reads 0 changed lines, and the code blobs are identical at both heads.main, first lineFixes #20679, second lineClause-②: yes (widening), and no other closing keyword (scanned).metadata-protocolmethod (with a notice todomain:engine,5905804651), thePOST /bounded in-place fix, and the two test-only pins.@objectstack/metadata-protocolminor,@objectstack/runtimepatch.check-governed-merges --pr 20817: NOT governed.mergeable_statereadsclean.access-security.packaged-flow-write-door-parity: clauses 1–4 pass over the booted showcase in the dogfood pin. Its "EXPECTED FAIL today" notes go stale on landing, a checklist-author follow-up. Noted, not filed.@objectstack/restinlines the protocol class: a pre-existing packaging shape with no measured defect. Acceptance notes, dropped as a card: it is outside the three classes.Fixesdoes not, removespm:dispatched, and releases automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761 (Blocked-by: #20679) back topm:queuefor its stage 2.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
Landed: PR #20817 →
4b45afaed5(the packaged-flow lock on the automation definition doors)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T09:54Z4b45afaed5e2a1afdbc8c4fd36005dce210baa4eis onorigin/mainas a single-parent squash over73155fedc. Content:git diff --statof the PR heade4006d68against4b45afaeover the PR's 7 files is empty.packages/runtime/src/domains/automation.tsonorigin/mainnamesrefusePackagedFlowBaseChangeon 5 lines, against 0 at the parent.packages/metadata-protocol/src/protocol.tsnamespackagedBaseRefusalon 5 lines.completedand removespm:dispatchedin the same act.Blocked-by: #20679) returns to thedomain:cliseat for its stage 2, per the ruling5904938166on that card.Generated by Claude Code
fix(runtime): mount POST /automation/:name/clone on the dispatcher br…
fix(runtime,metadata-protocol): the /automation write doors keep the …
fix(automation): which flows are packaged is the loader's fact, and e…
fix(runtime)!: the /automation create and update doors save the flow …
fix(metadata-protocol)!: refuse a flow saved into a package this depl…