Repository navigation
access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679
Description
Activity
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsTriage: 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:- 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.
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 29, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClaim: 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_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: thePUT /:nameandDELETE /:namearms only (the definition-write and removal doors), plus a new pin file beside it inpackages/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(triage5895656793: boot registers packaged flows through it). .changeset/20679-*.mdfor@objectstack/runtime.- Added in the same round (a bounded in-place fix, all four conditions checked by the seat): the
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. - 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) 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. - Added, cross-lane (the claiming seat carries it to landing):
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). - If the predicate's real home is not importable from
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 commented
on Sep 30, 2026 ContributorAuthorMore actionsos-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 commented
on Sep 30, 2026 ContributorAuthorMore actionsos-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 commented
on Sep 30, 2026 ContributorAuthorMore actionsACCEPT: PR #20817 at
e4006d68(the packaged-flow lock on the automation definition doors)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T09:28Z- Contract review of record:
5908296340on the PR,CONTRACT_REVIEW_TIER, heade4006d68, 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 -wof both lifted package doors against the merge-base reads 0 changed lines, and the code blobs are identical at both heads.
- It is a delta re-review over the review of
- Checklist:
- Draft, base
main, first lineFixes #20679, second lineClause-②: 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-protocolmethod (with a notice todomain:engine,5905804651), thePOST /bounded in-place fix, and the two test-only pins. - Changesets:
@objectstack/metadata-protocolminor,@objectstack/runtimepatch. check-governed-merges --pr 20817: NOT governed.- 35 check runs on the head: 32 success and 3 skipped.
mergeable_statereadsclean.
- Draft, base
- 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. - Out-of-scope findings:
- The refusal sentence names neither clone nor the toggle: filed The packaged-base refusal for a flow (403 NOT_OVERRIDABLE) prescribes "edit the source artifact and redeploy, or set OS_METADATA_WRITABLE", not ADR-0126 Regime C's sanctioned path (clone, or the enable/disable switch) #20819 (class b).
- Two envelopes for one refusal: pre-existing. Acceptance notes, 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.
- Next: land through the queue. At the merge, the seat closes this card if the
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
- Contract review of record:
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsLanded: PR #20817 →
4b45afaed5(the packaged-flow lock on the automation definition doors)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T09:54Z- Landing reading:
4b45afaed5e2a1afdbc8c4fd36005dce210baa4eis 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. - Closure: the merge did not close this card by itself (the same reading as runtime: POST /api/v1/automation/:name/clone is not mounted on the HTTP server — every flow clone, from the API and from the Setup packaged-automation page, answers 404 ENDPOINT_NOT_FOUND #20676 and [finding] os migrate meta aborts at load on an object built with ObjectSchema.create that carries a retired key, although the refusal it prints tells the author to run os migrate meta #20696 today). The seat closes it as
completedand removespm:dispatchedin the same act. - Unlock: 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) returns to thedomain:cliseat for its stage 2, per the ruling5904938166on that card.
Generated by Claude Code
- Landing reading:
- added 5 commits that reference this issue
on Oct 7, 2026
QA-source: #20674 · access-security.packaged-flow-write-door-parity · c2, c3
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 onmainat6bff748b(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.mdrule 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).
Generated by Claude Code