Skip to content

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

Closed

Description

@objectstack-fleet

Filing gate: ① a product defect with a measured reach. Class a. Security family. Filed by the domain:services seat (#6021, session session_01XY5uCwTjZj7884yYtyur4H), as triage's direction on #20725 (5901347976) requires: "that is filed as its own security card, abstract and with no request detail. ⛔ Not fixed inside this card."

⚠️ Disclosure discipline. This card, and every comment and PR that follows it, carries no request body, header or field spelling. The measuring dev keeps the evidence in its private scratchpad.

What was measured

Measured by #20725's dev (report 5903162549) through the automation create door on a showcase boot, as the seeded administrator.

A create request whose body asserts package provenance yields a flow that the engine treats as package-shipped, beyond its classification:

  1. The ADR-0126 §7.3 guards count it as a shipped caller.
  2. The toggle door accepts it and writes an activation row attributed to the asserted package. sys_metadata_activation declares no tenant column: its rows are deployment-wide. The same flow created without the assertion is refused by the toggle door (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).
  3. The same request without the assertion does neither.

Not affected, as measured

  • Precedence against a same-named shipped flow does not depend on the assertion.
  • Read-only package treatment applies to neither kind of flow on these doors.

Not measured, for triage's first grade

  • Who can reach the create door. The measurement ran as the seeded administrator. Whether a tenant author below platform-operator rank can reach it, and whether the toggle door's ADR-0126 §5 authority gate stops them, was not measured.
  • Cross-tenant effect. Whether a deployment-wide activation row attributed to a real package's name can change that package's shipped flow for other tenants was not measured. The deployment-wide scope is read from the table's declaration.
  • Other doors. Whether other metadata types' write paths accept the same assertion was not measured.

Direction (proposed, triage's to set)

The provenance of a flow is established by the platform (package install, hydration), never asserted by a client on the authoring write path. Two possible directions:

  • the create and update doors refuse, or strip, a client-asserted package provenance;
  • or the provenance the guards read comes from a server-held fact, not from the body.

Pins would cover the create door with the assertion (refused, or classified as customer-authored) and the toggle door on such a flow (refused as customer-authored, per #20726).

Reader who acts

Triage's first grade: it sets the grade and the lane. The fix may land at the authoring write path (packages/runtime/src/domains/automation.ts or the metadata write-path envelope), not in service-automation.

Dedupe (queries run before filing, closed included)


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Disclosure hygiene · domain:services seat (#6021) · session_01XY5uCwTjZj7884yYtyur4H · 2026-09-30T03:18Z · ⛔ not a claim

    Two public surfaces written before this finding was known to be security-family carried its request-level detail. This seat redacted both through the fleet relay, and each now reads abstractly and points here:

    What remains for a human: GitHub keeps the pre-edit text in each item's edit history, which anyone with read access can open. Purging those revisions takes a person with write access (the "delete revision" action in the edit history). No seat tool can do it. This seat raises it with the maintainer in the round report.

    The at-tier record on PR #20759 (5903322757), the dev report on #20725 (5903162549) and this card carry no request detail.


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · security · priority:p1 · domain:cli · area:access · pm:queue. Direction: a flow's package provenance is a server-held fact, never the body's. Reach is measured first, and p0 if a lower tier or another tenant reaches it

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T03:54Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: the authoring write path is served by packages/runtime ⇒ domain:cli. That is the lane and the door family of #20679, and the two are coordinated. The engine is consulted, not edited, unless the dev shows the one server-held fact lives there. This grade keeps the card's disclosure discipline: no request body, header or field spelling here or in any PR.

    Why p1, and when p0.

    • By the security rule, a measured security card is at least p1.
    • Today it is reached as the seeded administrator. Through one door, that actor can make a customer-authored flow count as package-shipped. It then holds a shipped artifact hostage under the §7.3 guards, and gets a deployment-wide activation row attributed to a package.
    • The two readings that decide p0 were not measured:
      • whether an author below platform-operator rank reaches the create door, and whether the toggle door's ADR-0126 §5 gate stops them;
      • whether a deployment-wide row attributed to a real package changes that package's shipped flow for another tenant.
    • They are the first step of this card. Either one reached makes it p0, reported to the seat post at once.

    Direction.

    1. Measure the reach first. Measure the two readings above, plus the other metadata types' write paths, on a real boot. Keep the evidence out of every public surface.
    2. One server-held fact. Provenance comes only from what the platform established: package install and hydration.
      • On the authoring write path, a body's provenance that agrees with the server-held fact is a round trip (a no-op).
      • A body's provenance that disagrees, or that names a package for a flow no package ships, is refused loudly, with an existing ADR-0112 code. ⛔ It is never silently honoured, and ⛔ never silently stripped.
      • The dev measures what the Studio and the CLI send on round trips before fixing the refusal's edge, so a legitimate round trip never breaks.
    3. One seam. If other metadata types accept the same assertion, the check goes at the shared write-path envelope, once, not per type. The report names the seam.
    4. Pins:

    Release. A lock the platform promises, bypassed through a door, in shipped versions. It goes on the release list with #20679.

    Also owed, by a human. The pre-edit revisions of the two redacted surfaces (5903378405) need a person with write access to purge them. The seat post carries that to the maintainer.

  3. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    priority:p1High: required for production / M2
    and removed on Sep 30, 2026
  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p1 · security, stage 1 of 2, the MEASUREMENT triage's direction puts first (5903721942, direction 1)
    Session: session_01VvcEokUG1tvVxkceYfR5XB
    Account: huangyiirene
    Branch: claude/issue-20761-provenance-server-held
    Worktree: objectstack-issue-20761
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: stage 1 edits no tracked file and opens no PR. It measures, on a real boot, the two readings that decide p0, the other metadata types' write paths, and what the Studio and the CLI send on round trips. It then names the one server-held fact and the one seam the fix belongs at. The evidence stays in the dev's private scratch space. The report and every public surface stay abstract: ⛔ no request body, header or field spelling (this card's disclosure discipline).
    Stage 2 (the fix) is dispatched on this same claim and branch once #20679 has merged. Its file surface is declared then, from stage 1's seam reading.
    Container & model: M, mode:subagent, model: default tier (opus) (dispatch-gates --tier at 0d9349fe: no path-derived mandate). Security-boundary card.
    Clause-②: no
    Clause-② reading: the direction refuses a client-asserted provenance the platform never established. That is a runtime integrity and authorization refusal (the negative boundary of clause ②), and it widens nothing. Stage 2 re-reads this line against its real diff: a new error code on the ledger would be yes.
    Thread-read: 5903721942
    Serial constraints cleared: Stage 1 touches no file. The fix is SERIAL behind #20679 (this seat, claim 5903676165, same area:access and the same door family): #20679 edits the PUT /:name and DELETE /:name arms of packages/runtime/src/domains/automation.ts, and this card's fix may land on the create and update doors of the same file or on the shared write-path envelope. Stage 2 waits for #20679's MERGE, not its arm. #20726 (domain:services, claim 5903568667) refuses a customer-authored flow at the toggle door, and this card's pins lean on that refusal.

    Taken 2026-09-30T04:42Z, main 0d9349fe. If either p0 reading is reached, the seat reports it to triage and on this card at once, without the detail, as triage's direction asks.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    [Decision] Stage 1 stopped before the live measurement: how the two p0 readings get settled, and which refusal shape the shared write seam takes

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-09-30T04:52Z · ⛔ no request body, header or field spelling here (this card's disclosure discipline)

    Release: session session_01VvcEokUG1tvVxkceYfR5XB, claim 5904193049. Why: stage 1 ended without its live measurement, and the next step is a maintainer decision. Destination: needs-user-decision, and the assignee is cleared in the same act. The branch claude/issue-20761-provenance-server-held carries only the empty claim marker. Whoever takes stage 2 re-claims.

    What stage 1 delivered. Source readings at origin/main f7c6d65f, with no live boot:

    • The automation create and update doors hand the body to the engine without going through the shared metadata write door.
    • The engine classifies a flow as package-shipped from provenance the body itself carries.
    • The /meta write door already removes that provenance without saying so, and marks each hydrated row as tenant-authored.
    • So the automation doors are the one unguarded path. The other types go through the shared door.
    • The dev read that the default organization-administrator set does not carry the metadata-authoring capability. ⚠️ The seat did not verify this.
    • The dev read that the activation ledger's package attribution changes no runtime behaviour, only what it displays. It was not measured live.

    Why the live half is missing. The harness's safety classifier stopped the dev partway through planning the live probes. The dev did not continue, and the seat has not routed around that stop: no re-dispatch, and no probe of its own. The live measurement was triage's stated first step, so it now needs the maintainer.

    维护者速读 / 决策分析

    一句话问题: 「平台管理员以外的人能不能把自己写的流程冒充成已发布包的流程」这个安全问题,本该先实机测清楚,再决定是不是 P0。测量被运行环境的安全拦截中止了,现在要你决定怎么补这一步,以及修复时拒绝的形状。

    Governing text:

    • 分诊方向 5903721942(照录):「Measure the reach first. Measure the two readings above, plus the other metadata types' write paths, on a real boot.」以及「refused loudly, with an existing ADR-0112 code. ⛔ It is never silently honoured, and ⛔ never silently stripped.」
    • AGENTS.md Prime Directive Add comprehensive test suite for Zod schema validation #12:「reject it at authoring/publish (validation / lint) so the error surfaces loudly」。
    • ADR-0126(包基线锁定)。

    前提(每条带复查命令与阳性对照):

    1. automation 创建/更新门不走共享写门:git grep -c saveMetaItem origin/main -- packages/runtime/src/domains/automation.ts → 0;对照 git grep -c saveMetaItem origin/main -- packages/runtime/src/domains/meta.ts → 8。
    2. /meta 写门静默剥除 provenance:git grep -n "function stripDerivedProvenance" origin/main -- packages/metadata-protocol/src/protocol.ts → 1 处;对照 git grep -n "function isCodeArtifactBody" origin/main -- packages/metadata-core/src/code-artifact-provenance.ts → 1 处。
    3. 默认组织管理员不带元数据作者权限:席位未验证(dev 源码读数)。

    问题 1:两个 P0 读数怎么补

    选项 做什么 客户可感知的后果
    A 你明确授权实机测量,席位引用你的原话,重派一次只测不修的 dev 多一轮(约 1 小时)。拿到实测后可定 P0 与否,并决定是否需要安全公告
    B 不补实机测量,按源码读数维持 p1,直接进第 2 阶段修复(排在 #20679 合入之后) 修复最快落地,落地后两个读数都不再成立;代价是「当时是否 P0」永远只有推断
    C 由你(人)在本地实例上跑这两个探测,只回贴抽象结论 占用你的时间;结论最可信

    业务直译:A = 先请质检出报告再定级;B = 先把门锁换了再说;C = 老板亲自验货。

    四轴:

    • ① 项目长远合理性:终态与测不测无关,都是「provenance 只由服务端事实决定,冲突即响亮拒绝」。测量只影响紧急度,而 P0 也不能越过同文件串行,所以第 2 阶段无论如何都排在 access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679 之后。测量结果不改变落地次序。
    • ② 实际业务拉动:今天已知能触达的是被显式授予作者权限的管理员;源码读数表明更低层级到不了、账本归属只影响显示,但都未实测。
    • ③ 防 AI 犯错:修复本身就是把静默采信改成响亮拒绝,与测不测无关。
    • ④ 不扩散:B 不新增任何探测基建;A、C 各多一轮一次性工作。

    推荐:B。回退:A。 只看①选 B;②③④ 是否翻转:否(②的未实测是置信缺口,不是翻转理由)。
    置信缺口: 若更低层级其实能触达,B 会让这张卡当时被低估为 p1。这影响的是发布说明与是否发安全公告,不影响修复次序。选 B 时,席位把「P0 未经实测」写进卡与发布清单备注。

    问题 2(同笔裁定):共享写接缝上拒绝的形状

    选项 做什么 后果
    A 在一个共享接缝上比较 body 与服务端事实:一致即无操作(正常往返不受影响),不一致即用既有 ADR-0112 错误码响亮拒绝;/meta 的静默剥除同步改成这条规则 两扇门一种行为;写错 provenance 的客户端会收到 4xx,而不是悄悄被改
    B /meta 保留静默剥除,只在 automation 门上拒绝 两扇门两种行为,同一个错误一处报错、一处吞掉
    • ① 长远:A 是一个接缝、一条规则;B 在同一概念上留下两种方言。
    • ② 拉动:Studio 与 CLI 的往返 body 带什么未实测,第 2 阶段必须先量(分诊已要求)。
    • ③ 防 AI 犯错:A 是响亮拒绝;B 在 /meta 上继续静默吞错,正是 AI 批量写错时被掩盖的形状。
    • ④ 不扩散:两者都不新增能力;A 改动 domain:engine 的 metadata-protocol,属跨车道,由分诊指定承接方式。

    推荐:A。回退:B(仅当第 2 阶段实测表明 Studio 往返会因此大面积报错)。 只看①选 A;②③④ 是否翻转:否。

    os-decision-facets
    ① 项目长远合理性:问题 1 选 B 不增特例,问题 2 选 A 把两扇门收成一个接缝一条规则,契约面缩小。
    ② 实际业务拉动:今天可触达者为被授权作者的管理员(实测于 #20725);更低层级与跨租户影响未实测。
    ③ 防 AI 犯错:冲突的 provenance 响亮拒绝,优于静默采信或静默剥除。
    ④ 创业阶段不扩散:不新增门禁、不新增能力,只改拒绝形状。

    Prior rulings read: provenance strip, stripDerivedProvenance, package provenance assert → #16702 (a triage-graded fix, no maintainer ruling in its thread) and #20725 (the triage direction 5901347976); ADR-0126, none on client-asserted provenance; thread: 3 comments before this one.

    裁后执行:


    Generated by Claude Code

  6. 16 remaining items

  7. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p1 · security, stage 2 of 2 (the fix), under the maintainer's ruling B + A recorded in 5904938166, with triage's order on area:access (5909424428: this card first)
    Session: session_01VvcEokUG1tvVxkceYfR5XB
    Account: huangyiirene
    Branch: claude/issue-20761-provenance-server-held (stage 1's branch; it carries only merged history, so the dev fast-forwards it to origin/main)
    Worktree: the cloud session's own checkout (mode:cloud)
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: ONE PR with its cross-lane surfaces declared, rather than per-layer children. That is ruling rule 6's choice, left to the claiming seat. The ruling asks for one rule in one shared function, and #20679 already put the locked-base verdict on the metadata protocol for reuse (packagedBaseRefusal).

  8. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20761,
      "status": "done",
      "branch": "claude/issue-20761-provenance-server-held",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20853",
      "session": "session_016SuKjJJSq2iLTm4Z2PbAmY",
      "premise_still_valid": true,
      "summary": "Ruling B + A (5904938166), stage 2, implemented as one PR with the declared cross-lane surfaces. (1) The automation engine classifies a flow by the loader's set only: setPackagedFlowSource / packagedFlowOwner replace every body-stamp reading (the §7.3 guards in both directions, the arming gate, the toggle door, and the activation row's package). The plugin hands it packagedFlowReader, which asks the metadata protocol's new packagedArtifactOwner at question time (the same registry lookup packagedBaseRefusal reads). (2) One shared rule, tenantAuthoredWriteRefusal in metadata-protocol, is asked by the automation create, update and clone doors and by saveMetaItem for flows. A name in the set gets packagedBaseRefusal's own answer (403 NOT_OVERRIDABLE), so a shipped flow's round trip is refused as a locked base. A name not in the set, with stamps that classify the body as code-shipped, is refused 422 INVALID_METADATA, an existing metadata-protocol code, and nothing is written. Stamps that agree with the server's fact are a no-op: a stored row of that name bound to the stamped package, or the base the write names. That edge was MEASURED: a package-bound tenant flow is served with its binding as a package stamp and no tenant marker. Other /meta types and the two server-stated row rewrites are untouched. (3) The clone copy is judged by the same rule and saved as a tenant row through protocol.saveMetaItem, env-wide, with rollback if the save fails. It reads back on /meta and survives a cold boot. The update door's own persistence was NOT built (claim allowance); it is measured and named below.",
      "tests": "All at HEAD f0de8fe69 (origin/main 30839063b merged). metadata-protocol vitest: 192 files passed | 3 skipped, 2813 passed | 19 skipped, exit 0. service-automation vitest: 158/158 files, 1984/1984, exit 0. runtime vitest: 298/298 files, 4961 passed | 1 skipped, exit 0. dogfood (flow-provenance-server-held, packaged-flow-write-door-parity, automation-flow-clone-door, automation-toggle-tenant-scope, packaged-activation-ledger-reach): 5/5 files, 42/42, exit 0. typecheck of metadata-protocol, service-automation (with check:test-typecheck), runtime and dogfood: exit 0. Ablation, one-shot and not kept: the five fix sources (protocol.ts, automation.ts, engine.ts, plugin.ts, service-automation index.ts) were restored to the merge base on the working tree, with a trap restore. Build exit 0. ablation-dist-preflight --absent passed for metadata-protocol (tenantAuthoredWriteRefusal), service-automation (setPackagedFlowSource) and runtime (tenantAuthoredWriteRefusal). The dogfood pin file then read 8 failed / 5 passed of 13. The 5 that pass both ways are preservation pins: the two locked-base refusals, the two /meta round trips, and the other-type control. Restore: git status --porcelain empty, preflight showed the markers present in dist again, and the file read 13/13. The first ablation attempt was VOID: service-automation's build went red on the unreverted barrel export, so runtime dist kept the fix. It was redone with the barrel included, and that is stated in the PR.",
      "mcp_calls": "0 — no GitHub MCP tool was called (card and comment reads were unauthenticated REST GETs)",
      "api_writes": "3 relay strokes, each ONE repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (#20853, draft, body read back byte-identical, 11082 bytes); (2) label-write assign → POST /repos/objectstack-ai/objectstack/issues/20853/assignees (huangyiirene, read back MATCHES); (3) this os-dev-report → POST /repos/objectstack-ai/objectstack/issues/20761/comments. Plus git push (not REST) to the claim branch.",
      "open_questions": [
        {
          "question": "Which existing ADR-0112 code refuses a body claiming a package's provenance for a name no package ships?",
          "options": [
            "A INVALID_METADATA / 422 (metadata-protocol ledger row; the precedent is saveMetaItem's refusal of a layered read envelope, a body carrying server-owned keys)",
            "B VALIDATION_ERROR / 400 (standard catalog)",
            "C RESOURCE_CONFLICT / 409"
          ],
          "recommendation": "A, chosen. Axis 3: a loud 4xx naming the keys to remove, with no new code minted. Axis 2: it is the code this package already emits for bodies a client may not persist. B would put a second dialect on the same door. C is wrong because the name is free and no current state is contested."
        },
        {
          "question": "How does the rule tell a legitimate round trip from a claim, given the measured echo of a package-bound tenant row (package stamp, no tenant marker)?",
          "options": [
            "A Agreement with the server's fact: a stored row of that name bound to the stamped package, or the base the write names",
            "B Literal reading: any code-shipped classification is refused",
            "C Refuse only stamps naming a read-only (managed) package"
          ],
          "recommendation": "A, chosen. It is what the ruling means by 'agree with the server's fact'. B was measured to break a legitimate round trip (422 on the showcase). C would still let a body claim any non-managed package that the server never bound. It costs one store read, and only on the rare stamped path."
        },
        {
          "question": "What does an engine with no loader's-set reader attached treat as packaged?",
          "options": [
            "A Nothing (fail closed: guards protect nothing, the toggle door refuses every flow)",
            "B Fall back to body stamps"
          ],
          "recommendation": "A, chosen. B reintroduces exactly the trust this card removes. Every real composition with objectql has the protocol service. Suites that pin guard logic attach an explicit stand-in (loader-set.test-support.ts), and packaged-flow-source.test.ts proves the engine reads the attached set, not stamps."
        },
        {
          "question": "Do the stored-metadata migration and package duplication (server-stated rewrites of rows already stored) go through the rule?",
          "options": [
            "A No, exempt by their server-stated source / writeFace",
            "B Yes"
          ],
          "recommendation": "A, chosen. They are not authoring doors, and a pre-#16702 stored row may carry stale stamps. B would turn those rewrites into failures, where today they strip silently."
        },
        {
          "question": "Grade of @objectstack/runtime in the changeset",
          "options": [
            "A patch (no public surface change; behaviour fix)",
            "B minor"
          ],
          "recommendation": "A, chosen. metadata-protocol and service-automation carry the new public members at minor under Clause-②: yes (widening). check-changeset-no-major exit 0 locally; the PR-scoped level axis is CI's."
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: POST /api/v1/automation answers 200 for a new flow, and after a cold boot on the same database file GET /automation/NAME answers 404; PUT /api/v1/automation/NAME on a flow stored through /meta answers 200, and after a cold boot the stored definition wins (both measured on the showcase via bootStack with a database file at f0de8fe69) · evidence: the create and update doors register in the engine only and write no metadata row (seat reading 5905846738 item 2, now measured) · dedupe words: automation create update door not durable, flow lost after restart, registerFlow no sys_metadata row, update door persistence",
        "carrier: none · the claim allowed naming it rather than building it; the clone's save does not cover these two doors for free, because making them durable changes both doors' contract. Also in the PR's Acceptance notes.",
        "class: a · reach: PUT /api/v1/meta/flow/NAME with a package query naming an id no installed package has answers 200 state=active (measured on the showcase host-config boot at f0de8fe69) · evidence: the row is stored bound to a package that does not exist, and its served body then carries that binding · dedupe words: meta save package id not installed accepted, orphan package binding sys_metadata, WRITABLE_PACKAGE_REQUIRED nonexistent package",
        "carrier: none · noted in the PR's Acceptance notes; not filed by the dev (seat files).",
        "carrier: none · observation, not filed: the loader's set is the SchemaRegistry view for both the #20679 lock and now the engine (one source, so they cannot disagree). A dev-time artifact reload that adds a code flow is not re-registered into that view until restart (source reading of the objectql and metadata reload hooks, not measured)."
      ],
      "gates": "dispatch-gates --commands at f0de8fe69: 69 derived; all 69 run with exit codes recorded; --ran reconciliation: 69 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero). Two first readings were not measurements and were rerun: check-plugin-teardown-shape --self-test exited 3 on the shallow clone and 0 after git fetch --unshallow; check:dual-build-cjs-loads answered PREREQUISITE NOT MET (eight unrelated packages had no dist) and exited 0 after they were built. pnpm lint: a proven narrowing, not a full run. (1) Population: every changed .ts is under packages/**, which eslint.config.mjs's packages/** and **/*.ts blocks lint. (2) eslint --no-inline-config --format json: 23 files, 0 errors, 0 warnings. (3) Invariance: the config never enables type-aware linting (eslint.config.mjs:327-328), so untouched files' verdicts cannot move. CI: in_progress at report time (not awaited, per os-dev).",
      "deviations": [
        "The claim names the cloud session's own checkout as the worktree; per os-dev rule 1 and the worktree guard hook, the work ran in a dedicated worktree ../objectstack-issue-20761 on the claim branch (the primary checkout was detached to free the branch). The branch was fast-forwarded f7c6d65f5 → bbe03f406 (ancestor check exit 0) before any edit; no force-push.",
        "#20679's own pin (runtime automation-packaged-base-lock.test.ts) asserted that a body claiming a package on the customer's flow is accepted with 200. Ruling point 2 reverses that, so the assertion now reads 422 INVALID_METADATA with the engine never entered. Its clone case stands in for the new save with a spy (the save is pinned in the new runtime file).",
        "Ten existing service-automation suites that pin §7.3 and toggle LOGIC with stamped bodies now attach a test-only loader's-set stand-in (loader-set.test-support.ts) on the specific engines that needed it; the set-vs-stamps contract itself is pinned separately.",
        "The metadata-protocol fold-population pin (protocol.read-verb-canonical-fold.test.ts) gains the two new folding verbs; its title count went from fourteen to sixteen. PR #20823's two files are untouched.",
        "The first ablation run was void (partial build); it was redone and reported as such."
      ],
      "files_changed": ".changeset/20761-flow-provenance-server-held.md; packages/metadata-protocol/src/{protocol.ts, protocol.tenant-authored-write.test.ts (new), protocol.read-verb-canonical-fold.test.ts}; packages/runtime/src/{domains/automation.ts, flow-clone.ts (header comment), domains/automation-tenant-authored-write.test.ts (new), domains/automation-packaged-base-lock.test.ts}; packages/services/service-automation/src/{engine.ts, plugin.ts, index.ts, packaged-flow-source.test.ts (new), loader-set.test-support.ts (new), and 10 suites attaching the stand-in: engine, engine-residual-log-cause, flow-activation-late-trigger, flow-activation-ledger, flow-label-on-result, flow-terminal-messages, node-type-vocabulary-seal-warning, subflow-guard-every-door, suspended-run-store, toggle-door-packaged-only}; packages/qa/dogfood/test/flow-provenance-server-held.dogfood.test.ts (new). 24 files, +1399 / -70. All within claim 5909772878's surface; no spec, objectql or metadata-core file.",
      "round_trips": "Studio: NOT MEASURED, reason: the Studio source lives in objectui, which is not in this container. The server-side echo Studio receives WAS measured on a showcase boot. A customer flow created through the automation door is served with no provenance stamps, on both read doors. A customer flow stored through /meta is served with none either. A shipped flow is served with its package's stamps. A package-bound tenant flow is served with its binding as a package stamp and no tenant marker; its round trip is accepted on both doors (pinned, including after a cold boot), and it read 422 under the literal rule before the agreement edge was added. CLI: source reading of packages/cli/src/commands/meta/register.ts:59-76 — os meta register sends the file verbatim to PUT /meta/:type/:name, and os meta get prints the served document. So the CLI round trip is the /meta round trip, measured above: a customer flow is accepted, a package-bound customer flow is accepted, and a shipped flow is refused as a locked base.",
      "seam": "loader's-set read: packages/metadata-protocol/src/protocol.ts ObjectStackProtocolImplementation.packagedArtifactOwner. The one shared rule: ObjectStackProtocolImplementation.tenantAuthoredWriteRefusal in the same file; its lock branch reuses packagedBaseRefusal, which gains an optional packageId for the named-base save. /meta applies it in saveMetaItem before the silent strip (authoring writes only). Automation doors: packages/runtime/src/domains/automation.ts refuseUnauthoredFlowWrite (POST /, PUT /:name, and clone), plus the clone arm's protocol.saveMetaItem. Engine: packages/services/service-automation/src/engine.ts AutomationEngine.setPackagedFlowSource / packagedFlowOwner / isPackagedFlow, wired in plugin.ts by packagedFlowReader (the kernel 'protocol' service, read lazily)."
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #20853 at f0de8fe6 (stage 2: a flow's package provenance is the loader's fact, and every authoring door refuses a claim the platform never made)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-09-30T13:05Z · ⛔ no request body, header or field spelling (this card's disclosure discipline)


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20853 → 76bd58fac4 (stage 2: which flows are packaged is the loader's fact; every flow written through an authoring door is tenant-authored)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-09-30T13:36Z · ⛔ no request body, header or field spelling


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:clipriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions