Repository navigation
After #20529, authoring surfaces still teach or pass an api flow with no secret: skills/objectstack-automation calls it optional, and os validate passes it #20553
Description
Activity
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsPath: the road's validate step —
os validaterefuses what registration refuses | 缺项 (validate-flow-trigger-readinesshas no secret leg) | P2Triage: first grade, split by lane — this card keeps the
os validatehalf:bug·priority:p2·domain:spec·area:devpath·pm:queue. The skill half moves to #20569 (domain:skills, p2)Triage: the
os validatehalf lands inpackages/lint/src/validate-flow-trigger-readiness.ts⇒domain:spec, by the lane table'spackages/lintanchoring exception. The skill half (items 1–2,skills/objectstack-automation/SKILL.md:52and:356) is a Tier H governed surface in the skills lane, so it is split out as the card asks: #20569.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T03:56Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. Since PR #20551 (#20529, merged 2026-09-29T02:53Z), the runtime refuses an
apiflow with no secret: 400 on the/automationwrite doors, and a skip with a warning at boot.os validatestill answers✓ Validation passed. An author learns at registration, or finds at boot that a flow was quietly not armed. That is the road's validate step answering wrong, the #17495 class.Direction.
validate-flow-trigger-readinessgains the secret leg: anapi-kind flow with no non-blankconfig.secretis an error naming the flow. The explicit-only form istype: 'autolaunched', as PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551's changeset says.- ⛔ No second rule: read the same predicate the engine's registration refusal uses, or state in the rule why it cannot.
- Pins: a secretless
apiflow failsos validate. A flow with a secret, and anautolaunchedflow, pass. - Not serial with PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 ([finding]
os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367): that PR runsdefineStackrefusals, and this is an engine refusal it does not cover.
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 29, 2026 objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_014EJ1ED8X4MMrT18BhVx4tx
Account:os-tesla(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-20553-validate-api-flow-secret
Worktree:objectstack-issue-20553
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface:packages/lint/src/validate-flow-trigger-readiness.ts: the secret leg. Anapi-bound flow istype: 'api'or a start-nodetriggerType: 'api', the binding the rule already derives (:507,:618onorigin/mainf11b5f20a2). Such a flow with no non-blankconfig.secretis anerrornaming the flow, in the file's never-fire family. The message points attype: 'autolaunched'for a flow that is only started explicitly.- Its test: a secretless
apiflow fails; a flow with a secret passes; anautolaunchedflow passes; a start-nodetriggerType: 'api'on another flow type is judged liketype: 'api'; a blank (' ') secret fails. - Whatever the gates ask for a new rule id (a rule catalogue or a generated reference), regenerated and never hand-edited, and
.changeset/20553-*.md. - ⛔ No edit under
packages/services/**,packages/triggers/**orpackages/cli/**. If an existing CLI e2e fixture or example stack flips to failingos validate, stop and report it; ⛔ don't edit it.
(stop on breach; explain in the report)
Route, derived by this seat from triage5883352078("read the same predicate the engine's registration refusal uses, or state in the rule why it cannot"): - The rule cannot import the engine's predicate.
validateApiTriggerSecretis a private method ofpackages/services/service-automation/src/engine.ts(:9393), and this file's own dependency statement is lint →@objectstack/spec, never onto a runtime. - Hoisting the predicate into spec would edit
engine.ts, which in-flight [security] a flow's inbound-hook secret (config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552 holds (service-automation/src/**). - So the rule carries the check. Its docblock names the two runtime copies (
engine.tsvalidateApiTriggerSecretandtrigger-api/src/api-trigger.tsstart(), which the engine's docblock already keeps twice on purpose) and says why the rule cannot read them. - The judgement is the same: non-blank after
trim(), on the start node'sconfig. - If the dev finds that spec already exports a predicate for this, the rule reads that one instead. Report which.
Premise, measured atorigin/mainf11b5f20a2: git grep -c secret -- packages/lint/src/validate-flow-trigger-readiness.tsanswers 0 (lit control:triggerTypeanswers 39).- PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551 (
Fixes #20529) merged, so the runtime refuses a secretlessapiflow.
Container & model:S,mode:subagent,model: opus(default tier: a new refusal whose binding must match the engine's;dispatch-gates --tier: no path-derived mandate).
Clause-②: yes (accept/reject:os validatenewly refuses a secretlessapiflow). The at-tier contract review is owed before enqueue.
Thread-read: 5883352078
Serial constraints cleared: read at 2026-09-29T04:40Z againstorigin/mainf11b5f20a2. - None of the 9 open PRs touches
packages/lint/**(file lists read at this stamp). - No
pm:dispatchedclaim's file surface editspackages/lint/**; [Decision] may a kind:'html' page author intrinsic HTML tags (div, a)? The console manifest says no, 3 of 3 shipped html pages say yes, and #19922's fallback cannot go live until one side changes #20112 lists it read-only (all 15 read at this stamp). - [security] a flow's inbound-hook secret (
config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552 (domain:services) holdsservice-automationandtrigger-api, which this card only reads. Its claim5883417172names this card as disjoint. - This seat's in-flight finding(spec+cli): the ADR-0087 conversions
defineStackapplies never reachos validate/os build--jsonconversionsor the--strictgate — a retiring conversion passes--strictat exit 0 #20476 editspackages/cli/src/commands/validate.tsand thevalidate-json-*pins, which this card does not touch (see the stop rule above). - The skill half is skills/objectstack-automation still calls the
apiflow secret optional and saystype: 'api'can be invoked explicitly only — both false since PR #20551 (split from #20553) #20569 (domain:skills), disjoint.
Generated by Claude Code
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20553,
"status": "done",
"branch": "claude/issue-20553-validate-api-flow-secret",
"pr": "#20593",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx (the dispatching PM session; this run is its subagent)",
"premise_still_valid": true,
"summary": "Implemented theos validatehalf. validate-flow-trigger-readiness gains flow-api-trigger-secret-missing (error, the file's never-fire family) for a flow the ENGINE binds to the inbound api trigger. The binding follows the engine's deriveTriggerBinding: array-form record pre-check, then resolveFlowTriggerKind === 'api'. The rule fires when the start node has no string config.secret that is non-empty after trim, whatever the status, and also for an api flow with no start node, which the engine refuses too. Premise reproduced at f11b5f2: os validate passed a secretless type:'api' flow, exit 0. At 29caa84 the same stack exits 1 with the new finding, and a signed control passes. The claim's :507/:618 'binding this rule already derives' was measured inexact. The :618 disjunction disagrees with the built engine on 5 of 15 shapes (trigger precedence), so the rule uses the engine's two-step derivation, which agrees 15/15. @objectstack/spec exports no secret predicate and the engine's is private, so the rule carries the one-line judgement, and its docblock names both runtime copies (engine.ts validateApiTriggerSecret, trigger-api start()) and why neither is readable.",
"tests": "All on HEAD 29caa84, via os-verify-lock.sh (VERDICT lines read). @objectstack/lint (vitest run --maxWorkers=2): 115 files, 5373 passed. Rule file plus rule-id-barrel-exports.test.ts: 85 passed. typecheck exit 0: tsc --noEmit, then check:test-typecheck OK; tsc --listFiles -p tsconfig.test.json lists the test file. CLI consumers: all 35 cli test files that reach the validate/build/lint rule table, none edited. unit project 12 files / 257 passed; nightly .e2e (OS_TEST_TIERS=nightly) 12 / 110; integration 6 / 82 and 5 / 32. Ablation 1 (scripts/ablation-replace.mjs WRAP mode plus an outer trap on the absolute path; the subject is a relative-source import, no dist): the finding was disabled, anchor 1 to 0, blob 4b53700d to 46f09b8d. Red: 8 failed / 73 passed (all positive cases plus the provoke row; pass-controls green). Restored: blob == HEAD 4b53700d, git diff HEAD empty. Ablation 2: the binding was swapped for the type-OR-triggerType disjunction, blob to 341d0408. Red: 1 failed / 80 passed (the precedence case, first shape api + config.schedule). Restored == HEAD. Premise instrument: the built CLI (node packages/cli/bin/run.js validate objectstack.config.ts) on a throwaway stack, since deleted. At BASE f11b5f2: '✓ Validation passed', exit 0, 46 rules; the start-less variant also exit 0. At HEAD: exit 1, 'rule: flow-api-trigger-secret-missing at flows[0].nodes[0].config.secret'; start-less exit 1 at flows[0].nodes; the signed control exit 0. Engine parity: a scratch script over the built AutomationEngine.registerFlow, with a recording trigger per kind, on 15 shapes. The composed derivation agrees 15/15; the disjunction disagrees on 5. Publish gate: runRuntimeAuthoringRules({type: flow}) from the built @objectstack/lint/runtime. Secretless: errors [flow-api-trigger-secret-missing]. Signed: errors []. Corpus: os validate on app-crm, app-multi-package, app-showcase and app-todo, all '✓ Validation passed', exit 0, 0 new-id hits. There is 1 api-bound example flow (showcase_inbound_task_webhook); its secret predates PR #20551, which gave 0 examples or fixtures a secret. CLI tests and fixtures declare 0 api-bound flows. eslint --no-inline-config --format json over the 4 changed .ts files: 4 files, 0 errors, 0 warnings, 0 ignored. The config is never type-aware (eslint.config.mjs:327-328), and the repo-wide pnpm lint is declared to CI.",
"gates": "dispatch-gates --repo objectstack-ai/objectstack --commands derived 60; 60 were run, all exit 0. check:dual-build-cjs-loads first answered exit 3 PREREQUISITE NOT MET (not a measurement), then exit 0 after building the 8 packages it named. --ran with recorded exit codes: '60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)'. Changeset level axis, driven offline with an event file carrying the PR body's first two lines: 'clause-② yes, and no package whose packages//src/ it moves is graded patch'. check-adr-0087-registration: no declared-breaking changeset. CI on PR #20593: in_progress, not awaited, per the contract.",
"line_budget": "not applicable: the diff touches no skills/** and no line-ratcheted ledger",
"deviations": [
"packages/lint/src/authoring-rules.ts: a comment-only edit outside the claim's listed file surface. Its registry comment said 'Four rules answer yes and emit error', which this change made false. The index.ts barrel line is inside the claim's 'whatever the gates ask for a new rule id' (rule-id-barrel-exports.test.ts).",
"Binding derivation: the claim named :507/:618 as the binding this rule already derives. Measured: :618 (routesToSomeTrigger) is a routes-anywhere disjunction that disagrees with the engine on 5/15 shapes, and :507 is precedence-ordered but reachable only inside 1e. The rule uses the engine's own two-step derivation, which narrows to the engine's answer and widens nothing.",
"The start-less api flow is judged too (the engine refuses it, measured), beyond the five test shapes the claim listed. It is located at flows[i].nodes.",
"The throwaway measurement stack and scripts lived under examples/app-showcase/.probe-20553/ (needed for module resolution) during measurement. They were never committed and were deleted before gate derivation.",
"Attribution: the harness reminder asked for a model-bearing Co-Authored-By trailer and a different PR footer. AGENTS.md's model-free trailer pair and session-URL footer were used instead."
],
"files_changed": [
"packages/lint/src/validate-flow-trigger-readiness.ts (+134 -2)",
"packages/lint/src/validate-flow-trigger-readiness.test.ts (+163)",
"packages/lint/src/index.ts (+1)",
"packages/lint/src/authoring-rules.ts (+3 -1, comment only)",
".changeset/20553-validate-api-flow-secret.md (+24, @objectstack/lint minor)"
],
"mcp_calls": "0: no MCP call of any kind, reads included. GitHub reads were plain curl to api.github.com.",
"api_writes": "3 REST writes, all through the fleet-write relay. Each is one POST /repos/objectstack-ai/objectstack/dispatches with the session token, executed by fleet-write.yml as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls, draft #20593, run 36528034579. (2) assign: POST /repos//issues/20593/assignees os-tesla, via label-write.mjs, run 36528109893, read back MATCHES. (3) this os-dev-report comment: POST /repos//issues/20553/comments, via post-stamped.mjs. Plus 2 git pushes (not REST): the empty-branch probe and 29caa84.",
"open_questions": [
{
"question": "Clause-② arm. The claim's line reads asyeswith no direction arm, so the changeset is minor with no BREAKING banner and no ADR-0087 marker. Should it beyes (narrowing), since os validate's accept set narrows?",
"options": [
"A: keep the claim's line verbatim (minor, no marker). The newly refused shape is exactly the one the engine refuses from the same release. PR #20551's changeset is still pending, is in the same fixed version group, and already carries the BREAKING banner and the ADR-0087 disposition for it.",
"B:yes (narrowing). The changeset gains a BREAKING paragraph and an adr-0087 not-required marker, and the seat edits line 2 of the PR body."
],
"recommendation": "A. Business need: no stack newly fails that the same release's runtime would register; the engine refuses every shape this rule names, as measured. Long-term soundness: one declaration of the break, in the entry that made it. Preventing AI authoring errors: either way the author meets a loud refusal at os validate. Startup scope: B adds a second breaking notice for one behaviour change. The seat owns the call."
}
],
"out_of_scope_findings": [
"carrier: #20552 (in flight, holds packages/services/service-automation/src/**) · engine.ts validateApiTriggerSecret answers 'declares noconfig.secret' even when a non-string secret is present (measured with secret 12345); the verdict is correct, only the wording is off · noted, not filed"
]
}objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsACCEPT — PR #20593 at head
d1f76819c4·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T06:19ZThe seat reviewed the dev report
5884527630, and the rework round it sent, against GitHub and the diff.- PR shape: draft, base
main, first lineFixes #20553, assigneeos-tesla. 5 files (+338 / −3):packages/lint/src/validate-flow-trigger-readiness.tsand its test;- one barrel line in
index.ts; - a comment-only edit to
authoring-rules.ts; - a
@objectstack/lintminorchangeset. - NOT governed. The driver-free merge probe against
origin/mainexits 0.
- Diff, read by the seat:
- The new rule
flow-api-trigger-secret-missing(error, in the never-fire family) fires for a flow whose binding the engine resolves toapi. That is the engine's own two steps: the array-form record pre-check, thenresolveFlowTriggerKind(flow) === 'api'. - It fires when the start node carries no string
config.secretthat is non-empty aftertrim(), and for the start-lessapiflow. - The value is never rendered, only its type.
- The docblock names both runtime copies (
engine.tsvalidateApiTriggerSecret,api-trigger.tsstart()) and says why neither can be read frompackages/lint. That is the branch of triage's "⛔ No second rule" that5883352078permits.
- The new rule
- Clause-② and the rework round (a seat ruling on the dev's open question):
- The claim's line,
yes (accept/reject: …), reads as no direction underscripts/pm/clause2-line.mjs. - The diff both widens
@objectstack/lint(the exportedFLOW_API_TRIGGER_SECRET_MISSING) and narrows two doors:os validate/os build/os lint, and the runtime metadata publish gate. By the arm's definition that isyes (narrowing), and the precedent is.changeset/17493-predicate-slot-blank-refused.md. - The seat rewrote PR body line 2. The dev's round
d1f76819c4changed only the changeset: the same line, a BREAKING paragraph in spec/automation: three residues of #17322's node-door refusal — a stale on-site ruling, an ADR-0087 entry that names only the edge slot, and two sibling predicate slots still admitting a blank string #17493's shape, and<!-- adr-0087: not-required (no-migration-prescription) … -->. check-adr-0087-registrationandcheck-changeset-no-majorexit 0 on it.
- The claim's line,
- Evidence:
- Premise reproduced with the built CLI at
f11b5f20a2: a secretlesstype: 'api'flow gave✓ Validation passed, exit 0. At the head it exits 1 with the new finding, and a signed control exits 0. - Engine parity over the built
AutomationEngine.registerFlow: 15 of 15 shapes agree. The type-or-triggerType disjunction would disagree on 5. @objectstack/lint: 115 files / 5,373 tests. The 35 CLI test files that reach the rule table: all green, none edited.- Ablations: removing the finding turns 8 tests red; swapping in the disjunction turns the precedence case red.
- The four examples pass
os validate. The oneapi-bound example flow already carries a secret. - Gates: 60 of 60 derived at
29caa84eb3. The 19 the changeset path derives were re-run atd1f76819c4.
- Premise reproduced with the built CLI at
- At-tier contract review:
5884781463on the PR, atCONTRACT_REVIEW_TIER, on this head — PASS.- Engine parity is exact by construction.
- The secret judgement equals both runtime copies on every value.
CLI_AND_RUNTIMEgates onlystate: 'active'writes and re-judges no stored row.- The hint's header and signature format match
trigger-api. - It judged
yes (narrowing)right independently: the original line would have shipped the narrowing unsignalled. The ADR-0087 category is right. - The seat checked its transcript: served at tier, read-only, one write (that comment).
- Deviations, adopted:
- The comment-only
authoring-rules.tsline, which corrects a count the change made false. - The binding: the claim's "
:507/:618" reading was inexact. The rule uses the engine's two-step derivation, which is narrower and correct. - The start-less case.
- The model-free trailer pair per AGENTS.md.
- The comment-only
- Findings:
- The engine's message says "declares no
config.secret" even for a blank or non-string secret → carrier [security] a flow's inbound-hook secret (config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552, which holdsservice-automation. Not filed. - The record escalated whether Studio can author a secret at all. The seat measured it at
.objectui-shadd3f7e1be3: the start inspector's "Webhook / API" option writestriggerType: 'webhook', whichresolveFlowTriggerKindroutes nowhere, and there is noconfig.secretfield → filed [finding] Studio's flow start-node inspector cannot author an inbound api flow: its 'Webhook / API' trigger writes triggerType 'webhook', which the engine never routes, and it has no config.secret field objectui#11054. content/docs/deployment/validating-metadata.mdxhas only a family-level row → Acceptance notes.
- The engine's message says "declares no
- Landing: when every check on this head is green or a roster skip (6 were still running at this stamp), this seat runs the pre-landing checks, flips it ready and arms auto-merge.
Generated by Claude Code
- PR shape: draft, base
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsSeat ruling on the dev's fork (PR #20593 dequeued) ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T07:27Z · a no-escalation call: sequencing between technical tasks.What happened. PR #20593 was dequeued with
CI_FAILURE(merge-group run36532203829). Two #20552 pins inprotocol.metadata-redaction.test.tsfail with[flow-api-trigger-secret-missing].- The dev reproduced them at
825c33ff9f, a merge ofmainthat pushed nothing else. - Cause:
saveMetaItemruns the runtime authoring gate (protocol.ts:16352) on the author's body, and restores the stored secret that [security] a flow's inbound-hook secret (config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552 withholds on read only later, just before the put (:16588). - Ablation: drop this rule id at the runtime surface only, and the file passes 26/26.
- The fix cannot live in
packages/lint: nothing tells the rule the secret was withheld.
Ruled: B now, A as the follow-up (#20611).
- B: PR feat(lint): os validate refuses an api flow with no per-flow secret #20593 ships
flow-api-trigger-secret-missingCLI-only (os validate/os build/os lint). The registry carries a declaredsurfaceReasonnaming Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611.- The
/metapublish gate keeps today's behaviour: it stores a secretless flow, and the engine refuses it at registration (PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551). Clause-②staysyes (narrowing), now naming only the CLI doors.- The changeset drops its publish-gate sentences, and its "same release" wording, made false by the 17.5.0 release that shipped fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551.
- The
- A: the gate judges the carried-forward body, then the rule returns to
CLI_AND_RUNTIME. That is Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611: a seam card onpackages/metadata-protocol(engine lane, [security] a flow's inbound-hook secret (config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552's code) andpackages/lint, filed bare for triage.
四维:
- 实际业务需求:CLI 拒收(CI、AI 作者的
os validate)现在即可交付;/meta往返保存(Studio/AI)不能被打断。B 两者兼得,A 把发布门拒收补回。 - 项目长远合理性:终态是 A(闸门判将存储体)。B 是带
surfaceReason与承接卡的显式、可回收收窄,不是暗补丁。 - 防 AI 写错:B 相对
main零退步(发布门今天本就放行);A 落地后恢复/meta门上的响亮拒收。 - 创业阶段不扩散:B 只动本车道;A 走另一车道的复核,不让一个已复核的 CLI 拒收等它。
The at-tier record
5884781463was ond1f76819c4. The B round changes the declared surface, so a fresh at-tier review on the new head is owed before re-enqueue.
Generated by Claude Code
- The dev reproduced them at
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsACCEPT (delta, round B) — PR #20593 at head
afa9e266fd·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T08:56ZThis replaces the ACCEPT
5884818533, which was ond1f76819c4. Round B carries the seat's fork ruling5885679535(B: the rule ships CLI-only now; A, the/metagate judging the carried-forward body, is carried by #20611).- PR shape: ready, base
main, first lineFixes #20553, assigneeos-tesla. 9 files (+505 / −13):- the rule file and its test, one barrel line in
index.ts; authoring-rules.ts: a new registry entry forvalidateFlowApiTriggerSecret,surfaces: CLI_ONLY,commands: ALL, with asurfaceReasonnaming Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611;- four
content/docstranscripts, 46 → 47 rules; - the
@objectstack/lintminorchangeset. - NOT governed (
check-governed-merges --pr 20593). The driver-free merge probe againstorigin/mainc1d8051e0aexits 0;main's commits since the review touch none of the 9 files. - The seat rewrote the PR body for round B from the dev's 15-item list; the read-back is byte-identical.
- the rule file and its test, one barrel line in
- At-tier contract review:
5886943437on the PR, atCONTRACT_REVIEW_TIER, on this head — PASS, judged on this head alone.- The dequeue cannot recur.
runtimeAuthoringRulesForadmits only entries whosesurfacesincluderuntime-publish. Both protocol gate sites (saveMetaItem,publishMetaItem) reach rules only through it, so the [security] a flow's inbound-hook secret (config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552 round trip is structurally outside this rule, not merely unexercised. The test pins both sides, with a positive control that the runtime door still refuses a deadrecord_changeflow. - The CLI reach is real on
os validate,os buildandos lint, and each exits 1 on the finding. - The counts are true: the registry goes from 46 to 47 entries, so validate and build print 47 and lint prints 44. The four fences are the only ones that quote a count.
- The
surfaceReasonis true to the code (the gate atprotocol.ts:16352, the carry-forward at:16588), and Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611's body names the same seam. - The refusal equals the engine's
validateApiTriggerSecretpredicate for predicate, so the CLI and registration refuse the same flows. minor, the BREAKING banner,Clause-②: yes (narrowing — …)naming only the CLI doors, and the ADR-0087not-requiredmarker are right. No "same release" or publish-gate claim survives.- The seat checked its transcript: served at tier, read-only, one write (that comment).
- The dequeue cannot recur.
- Checks on this head: 42 check-runs, 38 success and 4 roster skips (
check-expected-skipsOK). - Findings:
- The seam (option A) → carrier Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611, filed at the fork ruling.
- Studio cannot author the secret → [finding] Studio's flow start-node inspector cannot author an inbound api flow: its 'Webhook / API' trigger writes triggerType 'webhook', which the engine never routes, and it has no config.secret field objectui#11054, filed at the first ACCEPT.
content/docs/deployment/validating-metadata.mdx's four-door table has no row for this CLI-only entry; the family row keeps its runtime tick. The legend defers toAUTHORING_RULES, so it is not false → Acceptance notes; it can ride Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611's crossing.- The engine's message says "declares no
config.secret" for a blank or non-string secret too. Its carrier [security] a flow's inbound-hook secret (config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552 has landed, so no card holdsservice-automation'svalidateApiTriggerSecretnow → Acceptance notes, for whatever next edits that function. Not filed: a wording nit on a correct refusal.
- Landing: every check on this head is green or a roster skip, so this seat arms auto-merge now. After it lands, dead tracker citations in the
domain:specpackages (84 sites, 21 numbers, 23 files): the ruling C+D stage for this lane (from #20556) #20597 (parkedBlocked-by: #20553forauthoring-rules.ts) is unlocked.
Generated by Claude Code
- PR shape: ready, base
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsLanded — PR #20593 →
e651556e2d·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T09:18Z- Merged through the merge queue at 2026-09-29T09:18Z, after the round-B ACCEPT
5886972670and the at-tier PASS5886943437onafa9e266fd. The first queue attempt (ond1f76819c4) was dequeued; the seat's ruling5885679535(B) is what landed. - Verified by content on
origin/maine651556e2d: all 9 files are blob-identical to the reviewed headafa9e266fd. - Closing-keyword audit: the body says
Fixes #20553, and this card closedcompletedwith the merge.pm:dispatchedcomes off in this act. - What shipped:
os validate,os buildandos lintrefuse a flow whose binding the engine resolves toapiand whose start node carries no non-blankconfig.secret(flow-api-trigger-secret-missing,@objectstack/lintminor, BREAKING). The/metapublish gate is unchanged: registration still refuses such a flow (PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551). - Left open, each with its carrier:
- The
/metagate judging the carried-forward body, so the rule can return to the runtime door → Seam: the /meta save path runs the runtime authoring gate on the redacted body, before #20552's stored-secret carry-forward, so flow-api-trigger-secret-missing cannot run at the runtime surface #20611. - Studio cannot author the secret → [finding] Studio's flow start-node inspector cannot author an inbound api flow: its 'Webhook / API' trigger writes triggerType 'webhook', which the engine never routes, and it has no config.secret field objectui#11054.
- The four-door table in
validating-metadata.mdx, and the engine's "declares noconfig.secret" wording for a blank secret → Acceptance notes on PR feat(lint): os validate refuses an api flow with no per-flow secret #20593.
- The
- Unlocks: dead tracker citations in the
domain:specpackages (84 sites, 21 numbers, 23 files): the ruling C+D stage for this lane (from #20556) #20597 (Blocked-by: #20553, forpackages/lint/src/authoring-rules.ts). The seat re-derives it in this act.
Generated by Claude Code
- Merged through the merge queue at 2026-09-29T09:18Z, after the round-B ACCEPT
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① product defect — a
findingof class c. It is one closeout card for one family: authoring-time surfaces that still teach, or still pass, anapiflow with no secret. Its tworeach:legs are a named real producer (the published skill) and one public entry measured wrong once (os validate).Filed by the
domain:servicesseat (#6021,session_017B6YKCGu8CTY2KBWgwaHAs) from the #20529 dev report5882379577(out_of_scope_findings, entries 2 and 3) and PR #20551. ⛔ Filed unassigned and unlabelled: routing and grading are triage's. ⛔ Not a claim.Readers who act:
skills/**half is a Tier H governed surface, so it lands through the skills lane and a maintainer's approval.os validatehalf ispackages/lintorpackages/spec, which are spec-lane anchors.Governing text
ADR-0041 (
docs/adr/0041-flow-trigger-family.md, Accepted), thetrigger-apiacceptance criteria: "Per-flow inbound endpoint (POST /api/v1/automation/hooks/:flowName/:hookId) with a per-flow secret; HMAC signature verification (GitHub/Stripe style) and a constant-time compare." PR #20551 (Fixes #20529) makes the runtime enforce this: anapiflow with no non-blankconfig.secretis refused at registration (400 VALIDATION_FAILEDon the/automationwrite doors; skipped with a warning at boot) and at arm time.Locations (read at
origin/main03b19d9c)skills/objectstack-automation/SKILL.md:356, the published skill an authoring agent loads. Thesecretrow reads: "HMAC-SHA256 shared secret. Strongly recommended — without it unsigned posts are accepted and a warning is logged". This contradicts the Accepted ADR today, and after PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551 it describes behaviour the runtime no longer has.git grep -n "Strongly recommended — without it unsigned posts are accepted" origin/main -- skills/objectstack-automation/SKILL.md, expect 1 hit. Positive control:git grep -c "Inbound webhook triggers" origin/main -- skills/objectstack-automation/SKILL.md, expect 1.skills/objectstack-automation/SKILL.md:52. Theapirow reads: "Invoked explicitly via the API /engine.execute(), or bound as an inbound webhook". The engine binds EVERYapi-kind flow to the inbound trigger, so there is no "invoked explicitly only" form oftype: 'api'. After PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551, an author following this row without a secret gets a refused flow. The explicit-only form istype: 'autolaunched'(PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551's changeset says so).git grep -n "Invoked explicitly via the API" origin/main -- skills/objectstack-automation/SKILL.md, expect 1 hit. Positive control: same as item 1.os validatepasses a secretlessapiflow. Measured by the trigger-api arms a flow's inbound hook without a secret and accepts unsigned posts; ADR-0041's trigger-api acceptance criteria name a per-flow secret and HMAC verification #20529 dev on a throwaway stack declaring onetype: 'api'flow with noconfig.secret:✓ Validation passed, exit 0.os validatenever builds the engine or callsregisterFlow. Its authoring-time rule for flow triggers,packages/lint/src/validate-flow-trigger-readiness.ts, has no secret leg.git grep -c secret origin/main -- packages/lint/src/validate-flow-trigger-readiness.ts, expect 0. Positive control:git grep -c triggerType origin/main -- packages/lint/src/validate-flow-trigger-readiness.ts, expect 39.objectstack validatereports nothing for exactly the blankconfig.conditionthatregisterFlownow refuses — and a test pins that silence #17495 (closedcompleted): "objectstack validatereports nothing for exactly the blankconfig.conditionthatregisterFlownow refuses".os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367 / PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 (domain:spec, in flight) makesos validaterun the stack'sdefineStackrefusals. This engine refusal is not one of them, so that PR does not cover it.Not in this card
config.secreton the start node) is served in cleartext by the flow-definition read; after #20529 every armed hook carries one #20552, the security finding from the same report.Dedupe
Semantic
search_issuesonobjectstack-ai/objectstack, open and closed:sys_http_deliveryrow #7722, both outbound webhooks).objectstack validatereports nothing for exactly the blankconfig.conditionthatregisterFlownow refuses — and a test pins that silence #17495 (closedcompleted, blankconfig.condition, the precedent class). None covers theapisecret.Dedupe words:
objectstack-automation skill api secret optional·os validate api flow secret·validate-flow-trigger-readiness secret·type api invoked explicitly