Repository navigation
Description
维护者速读
- 事由: 引擎会在 flow 数据节点的写入上盖
HookContext.provenance.flowRunId,REST 写入没有这个标记。但沙箱里的 hook body 读不到ctx.provenance,所以只要守卫需要分辨「这次写入来自 flow 还是用户自己」,就只能打包发布(bundled),过不了lint --strict。 - 要做:
buildSandboxContext把ctx.provenance(至少flowRunId)传进沙箱,做法同现有的 referential markers;CLI 降低器extract-hook-body认这个键;在 hook body 契约文档里写明。 - 完成标准: 读
ctx.provenance.flowRunId的 hook 能降低成纯元数据 body;lint --strict不再报hook-body/bundled-fallback;在沙箱里 flow 写入能读到这个值,REST 写入读不到。
Filing gate ③ a task the maintainer set: ruling A on hotcrm#2285 (6407529478, batch #15 item 1, the maintainer's 「A」 of 2026-10-26T03:24:52Z), which orders the platform first. Filed by the repo:hotcrm seat 1 (os-elon-musk, session session_014xanhPEmGTMTfVH4UokwJT). ⛔ Not a claim. The grade and routing belong to platform triage. The seat suggests priority:p3, the downstream card's grade.
The consumer, measured on @objectstack/* 18.1.0
HotCRM's ruling says an account created by lead conversion skips account_approval, and a hand-created account still goes through it. The conversion flow runs runAs: 'user', so a rep can write through REST any field that create_account can write. The only mark that tells the two writes apart is server-stamped: HookContext.provenance.flowRunId.
The guard on hotcrm branch claude/project-thread-5o1adp @ 2002ca5 uses it. Booted in-process, 21 of 21 tests pass:
- the rep's Convert Lead creates the account with the marker and no approval request;
- a hand-made account is still
pending; - a REST create or PATCH that carries the marker is refused with
400.
Report: hotcrm#2285 comment 6398882683.
What fails is lowering. os lint --strict says: hook 'account_protection' is bundled, not shipped as a metadata-only body: ctx.provenance is not reachable from a sandboxed body (hook-body/bundled-fallback).
@objectstack/cliextract-hook-body.jsnotes "A body has no equivalent today".buildSandboxContext(@objectstack/runtime) copiesinput,previous,user,session,event,object,result,api,dispatchand the referential markers. It copies no provenance.sessioncarries noflowRunIdeither.
What to do
- Runtime:
buildSandboxContextpasses a read-onlyprovenanceinto the VM, with at leastflowRunId, plus whatever else the engine stamps that is safe to expose. A REST write seesprovenanceabsent or with noflowRunId. - CLI:
extract-hook-bodyacceptsctx.provenancereads and lowers them instead of falling back to bundled. - Contract: the hook-body context reference says what
provenancecarries, and that a value set by a flow node cannot be forged through the data API. - Tests: the same body runs in the sandbox and reads
flowRunIdon a flow write and none on a REST write.
Related
- Security: a runAs system hook or an action script body reaches the four identity-assignment objects past the delegated-administration gate (the #25251 refusal requires flowRunId) #25653: the identity-assignment gate already relies on
flowRunIdin-process. This card makes the same mark readable from a sandboxed body; it does not change that gate. - Downstream: hotcrm#2285 takes
Blocked-by:this card and resumes after the first installable release that carries it.
Dedupe
Method: every objectstack-ai/objectstack issue updated since 2026-10-10 (any state), listed with the repo-scoped issues endpoint and matched case-insensitively on provenance, flowRunId, sandbox … context and hook body … ctx, at 2026-10-26T03:35Z. 1 hit: #25653, a security gate that uses flowRunId in-process, which is not this. New.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
enhancement·priority:p2(not p3) ·domain:server·area:automation·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T03:53Z⛔ Not a claim, ⛔ not a dispatch.
Blocked-bythis card and the app guard waits on it. That is a ruled app feature held by a platform gap. It is not a release item, so it is not p1.ctx.provenance(at leastflowRunId) mirrors the existing referential markers. Keep it read-only in the sandbox, so a body cannot forge the mark.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 1 · card thread · 2026-10-26T04:13Z
Session:
session_0144xurUaQ5v2UtFYLpYp28J(the card thread, mode:thread; the seat writes this claim for it: seat sessionsession_01TFnDdepdwyDJeM2kJ4GtMy)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-23dgf0(platform-assigned to the card thread; a thread can push only its assigned branch, a measured platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-25905Domain:
domain:serverSeat:
domain:server#2(seat post #20966; 由 os-elon-musk 账户的项目负责)File surface (read on
origin/mainf00e8d5c; stop on a breach and explain it in the report):packages/runtime/src/sandbox/body-runner.ts, the hook face'sbuildSandboxContextonly (a read-onlyprovenancecarrying at leastflowRunId, absent or empty on a REST write);packages/cli/src/utils/extract-hook-body.ts(lowerctx.provenancereads instead of the bundled fallback); the hook-body context reference undercontent/docs/**; tests in both packages (the same body readsflowRunIdon a flow write and none on a REST write;lint --strictno longer reportshook-body/bundled-fallbackfor it); one changeset. ⛔ Nopackages/spec/**:HookContext.provenanceis already declared inpackages/spec/src/data/hook.zod.ts, so this honours a declared key in the sandbox; if the change needs a spec edit (docblock included), stop and report, because it would then owe a contract-tier review.Container & model:
M,mode:thread,model: default tier (dispatch-gates --tier on this surface: "no path-derived mandate"),effort: highClause-②: no
Metadata-protocol: none
Basis for the Clause-② line: the sandbox starts carrying a key the published
HookContextschema already declares (provenance,flowRunId); pulling back a declared contract is not Clause-②. Re-read against AGENTS.md on the measured diff; if the dev reads the lowerer's new accept as a widening arm, it says so in the report and the seat re-judges.Responsibility:
packages/runtime(buildSandboxContext) andpackages/cli(extract-hook-body) produce the gap | no platform path covers it: a metadata-only body cannot read the mark, so the guard must ship bundled | reached by hotcrm#2285's account-approval guard today (measured on 18.1.0, per the card)Thread-read: 6407839297
Premise: holds (
grep -n provenance packages/spec/src/data/hook.zod.tsonf00e8d5c: the key is declared at ~:1156–:1173; the card readsbuildSandboxContextcopying no provenance)Serial constraints cleared: seat 1's #25653 (claim
6407781630) editsbody-runner.ts's action face,objectql'swithRunAsandplugin-security's predicate. This card stays on the hook face'sbuildSandboxContext. Same file, different function: whichever lands second mergesmain. No open PR namesextract-hook-body.ts.Writes: PR only through the relay
pr_create; comments, labels and bodies only through the relay write tools (post-stamped.mjs,label-write.mjs,fleet-write/dispatch.mjs). ⛔ No GitHub MCP write tool. Report both counts (writes,mcp_calls).Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim amendment (file surface) ·
domain:server#2· seat sessionsession_01TFnDdepdwyDJeM2kJ4GtMy· 2026-10-26T04:15Z · amends claim 6408069263, same session, same branchclaude/project-thread-23dgf0.File surface adds, all in the sandbox path the claim already names. ⛔ Still no
packages/spec/**.packages/runtime/src/sandbox/quickjs-runner.ts:installCtx, hook face only.provenanceis installed as a frozen graft, the waydispatchand the referential markers reach a body.packages/runtime/src/sandbox/script-runner.ts: theScriptContexttype gains theprovenancefield.packages/cli/test/lowered-body-face-enumeration.test.tsandpackages/cli/test/hook-body-parameter-rebind.test.ts: their pins moveprovenancefrom refused to carried.Members: the three that
HookContext.provenancedeclares (flowRunId,attributedUserId,performedByClientId). Each is copied only as a string and is absent otherwise. In-process hooks already read the same declared members. Every other line of the claim stands.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25905, "status": "done", "branch": "claude/project-thread-23dgf0", "pr": "https://github.com/objectstack-ai/objectstack/pull/25952", "session": "session_0144xurUaQ5v2UtFYLpYp28J", "premise_still_valid": true, "summary": "The hook face's buildSandboxContext now projects the declared HookContext.provenance (flowRunId, attributedUserId, performedByClientId; each copied only as a string, the whole key absent when none survives) and installCtx grafts it frozen and non-writable like ctx.dispatch. extract-hook-body no longer refuses ctx.provenance, so a guard on ctx.provenance?.flowRunId lowers to a metadata-only body and os lint reports no hook-body/bundled-fallback for it. The docs page content/docs/automation/hook-bodies.mdx gains a Write provenance section. No packages/spec edit. The file surface grew by quickjs-runner.ts (installCtx), script-runner.ts (ScriptContext type) and the CLI pins, per claim amendment 6408086185.", "tests": "head df1c7164. New runtime pin src/sandbox/hook-provenance-signal.integration.test.ts (real ObjectQL + SqlDriver + QuickJS): the flow-shaped write reads flowRunId run_25905 after drift attempts; the REST write reads typeof undefined; the forged payload field provenance surfaces only on ctx.input. Ablation 1, buildSandboxContext falls back to JSON.parse(input.provenance): red at :165 (expected 'object' to be 'undefined'), blob 58df4905a51e to 81882a7eb7e0, restored to 58df4905a51e with an empty git diff HEAD. Ablation 2, freeze removed in the graft: red at :139 (expected 'replaced' to be 'run_25905'), blob 04f49a96f07d to 7c20c0ada09a, restored. Both import src relatively, so no dist was involved. runtime full vitest: Test Files 449 passed (449), Tests 8015 passed, 19 skipped. cli --project unit: 330/332 on the first run; published-subpath-console.pin and published-subpath-hook-body.pin threw 'packages/cli is not built' and passed (29 tests) after the CLI build. The integration tier is declared to CI.", "gates": "Each exit was captured before any pipe. typecheck (runtime and cli): exit 0. pnpm lint: exit 0. dispatch-gates --commands gave 98 families: 95 exit 0; check:i18n, check:i18n-coverage and check:i18n-walk-parity exited 3 (CLI dist absent) and exit 0 after the CLI build. NOT MEASURED, exit 3 prerequisite: check:dual-build-cjs-loads (unbuilt plugin dists), spec check:skill-examples (client-react dist absent), check-plugin-teardown-shape --self-test (shallow clone). --ran: 98 derived, 98 run, 0 unrun. PR check-runs: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read (get, get_comments x2); no GitHub MCP write", "api_writes": "2 relay strokes — fleet-write pr_create (POST /pulls + POST /issues/25952/assignees), and this os-dev-report comment via post-stamped", "open_questions": [ { "question": "The claim says Clause-②: no. The lowerer's accept set does widen: a handler that used to fall back to bundling now lowers, and the body face gains a readable key. Is that a (widening) arm?", "options": [ "A keep no: it honours a key HookContextSchema already declares; nothing new is authorable", "B yes (widening)" ], "recommendation": "A, because the published contract (HookContextSchema.provenance) is unchanged and the body face is pulled into line with it; but the seat re-judges per the claim's own note" } ], "out_of_scope_findings": [ "carrier: none · noted, not filed: a body can still define ctx.provenance itself on a write that carries none (the key is absent, same as ctx.dispatch). It stays inside that VM, so it is in the PR's Acceptance notes only" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim amendment (Clause-② line) ·
domain:server#2· seat sessionsession_01TFnDdepdwyDJeM2kJ4GtMy· 2026-10-26T05:35Z · amends claim 6408069263 (and amendment 6408086185), same session, same branchclaude/project-thread-23dgf0. Answersopen_questions[0]of the os-dev-report 6408739804: B.Clause-②: yes (widening)replacesClause-②: nofor@objectstack/runtime. PR #25952 adds the optional memberprovenance?toScriptContext, which the runtime barrel exports, andobjectstack buildnow lowers a handler it used to bundle.packages/specis still untouched: the member mirrors the already-publishedHookContextSchema.provenance. The level stayspatchunderMetadata-protocol: none(ruling A on #25105).@objectstack/clikeepsClause-②: nounless its built declarations moved. The declaration face owes aTIER_DEFAULTrecord, which this seat writes on the new head. Every other line of the claim stands.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
ACCEPT → PR #25952 comment 6409416101 ·
domain:server#2· 2026-10-26T06:07Z · declaration-face record PASS 6409406460 · landing through the relay (standing landing word, seat thread 2026-10-26 05:53Z)Generated by Claude Code