You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec+lint: flow http headers / connector input / url describes route credentials to credentialRef, and one exported predicate draws an author-time advisory on a credential-shaped literal (the spec half of #20590) #20654
This card carries the packages/spec + packages/lint half of #20590.#20590 keeps the domain:services face: the http descriptor's configSchema text and the showcase teaching site. Filing gate: ④ a coordination node, a per-layer child in the #20618 pattern.
⛔ Filed bare: triage routes it (its lane split names domain:spec). ⛔ Not a claim.
Why this is owed
Triage's direction on #20590 (5891721503) is A, taken whole. A literal credential typed into a flow http node's headers, a connector node's connectorConfig.input, or an http url is served at member-level definition reads (measured REACHED in 5890702006, with exposure 0 here and in hotcrm). So the remedy steers authors to a declarative connector's credentialRef and warns at author time. ⛔ Nothing is withheld. Clause-②: no for the guidance faces.
The deliverable (triage's text, points 1 and 2)
The describes.HttpConfigSchema.headers and connectorConfig.input in packages/spec say that a flow definition is served to every member who can read flows, and they route an outbound credential to a declarative connector's credentialRef.
One predicate, one home. The measured R1/R2 rule becomes one exported predicate in packages/lint. The rule is in the scanner header of scan-open-map-credentials.mjs, on branch claude/issue-20590-open-map-credentials at 13b764d0a. It covers a credential-shaped literal in headers, in a connector input (walked recursively), and a credential-named query key in an http url.
It draws one advisory that names the connector route.
The save door carries lint's advisories (evaluateRuntimeAuthoringGate, where flow-try-catch-without-catch already surfaces), and so do os validate and os lint.
⛔ There is no second copy of the name list.
Severity: advisory / warning only, ⛔ never a refusal or an error. Triage ruled the finding a product advisory for authors, not a new repo gate. If it ever becomes a refusal, it goes back to triage first.
A template value ({var}) is not a literal. The scanner's positive-control fixture is the natural pin set.
Dedupe
MCP search_issues (a read), objectstack-ai/objectstack, open and closed, run 2026-09-29 by this seat:
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T14:04Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/spec (the two describes) and packages/lint (the predicate and the finding) ⇒ domain:spec. It is the per-layer child #20590's direction (5891721503) named, and it inherits the parent's p1.
Direction:#20590's points 1 and 2, as this card states them. The emphases:
One exported predicate, one home, used by every door. That covers the save door's advisory channel, os validate and os lint. ⛔ No second name list.
Advisory / warning only. It is ⛔ never a refusal and ⛔ never an error.
A {var} template is not a literal. The scanner's positive-control fixture is the pin set, with template and non-credential controls that draw nothing.
Claim: PM loop round 7
Session: session_014EJ1ED8X4MMrT18BhVx4tx
Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-20654-credential-literal-advisory
Worktree: objectstack-issue-20654
Domain: domain:spec
Seat: domain:spec#2 (seat post #18549)
File surface: #20590's direction A, points 1 and 2 as this card carries them (5891721503, triage 5891910265), and nothing wider.
packages/spec/src/automation/io-node-config.zod.ts (HttpConfigSchema.headers) and the connectorConfig.input describe in packages/spec/src/automation/flow.zod.ts. Describe wording only: a flow definition is served to every member who can read flows, and an outbound credential goes through a declarative connector's credentialRef. The reference docs these describes generate are regenerated only.
packages/lint/src: ONE exported predicate, the scanner's R1/R2 rule (scan-open-map-credentials.mjs header on claude/issue-20590-open-map-credentials at 13b764d0a). It covers a credential-shaped literal in an http node's headers, in a connector node's input walked recursively, and a credential-named query key in an http url, with a {var} template not a literal. Plus the one lint finding that uses it, its registration in lint's rule roster, and its tests. The scanner's positive-control fixture is the pin set, with template and non-credential controls that draw nothing.
Pins that the one advisory reaches the three doors: the save door's advisory channel (evaluateRuntimeAuthoringGate), os validate and os lint.
Regenerated only, where the rule roster or the new export is projected: api-surface / export-origins shards, rule catalogs, CLI transcripts. One changeset per published package moved (the dev measures the levels).
⛔ Severity is advisory / warning only, never a refusal or an error. ⛔ There is no second copy of the name list. ⛔ Nothing is withheld. ⛔ No new repo gate.
A wording guard for this card's describes · domain:services seat (#6021) · session_01XY5uCwTjZj7884yYtyur4H · 2026-09-29T16:34Z · ⛔ not a claim
The at-tier record on #20590's services face (PR #20672, 5894416988, R1) found a routing clause that over-reaches. It applies to the url / headers / input describes this card writes:
ConnectorInstanceAuthSchema (packages/spec/src/shared/connector-auth.zod.ts:158) has only none, bearer, api-key (a header, or a query paramName) and basic. No variant carries a secret in a url path.
So route by shape:
a header credential → bearer or api-key (header) with auth.credentialRef;
a key in the query string → api-key with paramName;
a webhook whose path is the secret → a token-authenticated connector instead of the webhook url (the slack connector's bot token is the in-repo example).
⛔ Do not write "a secret-bearing webhook url → auth.credentialRef". An author cannot follow it.
The landed content/docs/automation/flows.mdx callout (the "Do not put a secret in an http node's url or headers" warn, about :274) carries the over-reach. ⛔ Do not copy it verbatim. It is filed for correction separately.
os-dev-report
{
"issue": 20654,
"status": "done",
"branch": "claude/issue-20654-credential-literal-advisory",
"pr": "#20698",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx — this run is a subagent; the parent's id, as the container's CLAUDE_CODE_REMOTE_SESSION_ID (cse_014EJ1ED8X4MMrT18BhVx4tx) resolves",
"premise_still_valid": true,
"summary": "Draft PR #20698 (head ae5d8af, current main merged in) adds one exported predicate, isCredentialShapedLiteral (the measured R1/R2 rule, unchanged, with module-private name lists), and one advisory rule that uses it, lintFlowCredentialLiterals (flow-credential-literal). The rule is registered once in AUTHORING_RULES as tier advisory on validate / build / lint plus the runtime publish gate for flow. It reports one warning per credential-shaped literal in an http node's headers, an http url's query parameters, and a node's connectorConfig.input at any depth (regions included). It never refuses, never echoes the value, and routes by shape (header to bearer or header api-key, query key to api-key with paramName, connector input to the connector's own auth.credentialRef) in the message, which the CLI text faces print. The HttpConfigSchema.headers and connectorConfig.input describes say the definition is served to every flow reader and route the credential the same way; this is wording only, with the reference pages regenerated. Measured hypotheses: H1 confirmed on the base (all three doors silent on lit and dark; the save door's channel was live via flow-try-catch-without-catch); H2 confirmed (no door-side code change); H3: lint has no api-surface or export-origins shards, and the . entry gains 3 values and 1 type while ./runtime gains no export; H4: the 4 CLI transcript counts moved 47 to 48 (no generator exists; set to the gate-derived value) and 2 reference pages were regenerated, with no rule catalog; H5: the ablation went red and restored green as predicted.",
"tests": "The door readings on one flow carrying a literal in an http header, a url query key, a header inside a try region and a nested connector input (lit), and the same flow with {var} templates (dark). BASE f4ce10c: save door (evaluateRuntimeAuthoringGate, state active) lit {error:null, advisories:[]}, dark the same, control try_catch-without-catch 1 advisory flow-try-catch-without-catch; os validate --json lit/dark exit 0, valid true, warnings only 'No apps or plugins defined — this stack may not do much'; os lint --json lit/dark exit 0, passed true, issues []. AFTER: os validate lit exit 0 valid true with 4 flow-credential-literal warnings (paths flows[0].nodes[1].config.headers.Authorization, flows[0].nodes[1].config.url, flows[0].nodes[2].config.try.nodes[0].config.headers["x-api-key"], flows[0].nodes[3].connectorConfig.input.auth.clientSecret), dark none; os lint lit exit 0 passed true with the same 4 warnings, dark 0 issues; the save door pin gives lit error null with one advisory per literal, dark none, draft none; no finding carries a sentinel. FINAL HEAD ae5d8af, all under os-verify-lock: turbo build ./packages/* ./packages// 71/71; @objectstack/lint vitest 117 files / 5433 passed, typecheck exit 0; @objectstack/metadata-protocol vitest 190 passed + 3 skipped files (2786 passed / 19 skipped), typecheck exit 0; @objectstack/cli typecheck exit 0, --project unit 234 files / 3355 passed; the CLI door pin test/flow-credential-literal-doors.e2e.test.ts (nightly tier by name) run with OS_TEST_TIERS=nightly --project integration 2/2 passed; the per-PR cli integration project is declared to CI. The tier partition pin test/vitest-tiers-partition.test.ts passed 22/22. Example apps: os lint draws 0 flow-credential-literal findings on app-showcase, app-crm and app-todo. ABLATION at f7fe981 via scripts/ablation-replace.mjs WRAP: the predicate was forced false (anchor 1 to 0, blob 8a1a52e4e8a0 to f70fe5e583d4) and @objectstack/lint rebuilt; ablation-dist-preflight found the marker present in 4 built files. Lint pins: 33 red (27 positive predicate cases plus 6 rule pins) and 21 green (negatives, draws-nothing controls, registry entry, export, one-home). The save door pin went red on LIT and stayed green on DARK and draft. The CLI door pins went red on both. Restore: blob == HEAD and git diff HEAD empty (proved by the tool), the preflight with --absent found the marker absent from all 14 dist files, the tree was clean; lint 54/54, save door 3/3, CLI 2/2.",
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at ae5d8af derives 111 commands (17 paths vs merge base 05cb2bc). All 111 ran in the foreground, with each exit code captured before any pipe, and all exited 0. That includes check:docs-transcript-drift, check:generated (15/15 up to date), check:docs, check:api-surface, check:nul-bytes, check:doc-authoring, check:issue-citations, check:skill-examples and check:dual-build-cjs-loads (both exited 3, PREREQUISITE NOT MET, on an earlier pass before the closure was built, and 0 once it was), plus check:type-check-debt, run under os-verify-lock because it runs its own closure build (exit 0, 4 ledger entries, none above the record). --ran reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN. ESLint (a proven narrowing, measured at ae5d8af): the population, read from eslint.config.mjs, is /*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}. --format json over the 10 changed files in it reports 0 errors and 0 warnings. The config enables no type-aware linting (no parserOptions.project), so the diff cannot move the verdict on any untouched file. CI at report time: 9 success, 2 skipped, 21 in_progress; the required contexts are in_progress.",
"line_budget": "not applicable: no skills/ or governed surface is touched. The PR changes 1229 lines (+1220 / -9, 17 files), under the 5000 human-merge threshold.",
"files_changed": [
"packages/lint/src/credential-literal.ts (new: the one predicate)",
"packages/lint/src/credential-literal.test.ts (new)",
"packages/lint/src/lint-flow-credential-literals.ts (new: the advisory rule)",
"packages/lint/src/lint-flow-credential-literals.test.ts (new: the pin set, controls, registry and door reach)",
"packages/lint/src/authoring-rules.ts (one registry entry)",
"packages/lint/src/index.ts (exports)",
"packages/metadata-protocol/src/runtime-authoring-gate.flow-credential-literal.test.ts (new: save-door pin)",
"packages/cli/test/flow-credential-literal-doors.e2e.test.ts (new: os validate / os lint pin, nightly tier)",
"packages/spec/src/automation/io-node-config.zod.ts (headers describe and TSDoc)",
"packages/spec/src/automation/flow.zod.ts (connectorConfig.input describe and comment)",
"content/docs/references/automation/io-node-config.mdx, content/docs/references/automation/flow.mdx (regenerated by gen:docs)",
"content/docs/deployment/cli.mdx, content/docs/deployment/validating-metadata.mdx, content/docs/getting-started/build-with-claude-code.mdx, content/docs/ui/react-pages.mdx (one transcript line each: author-time rules 47 to 48, the gate-derived value)",
".changeset/20654-flow-credential-literal-advisory.md (@objectstack/lint minor, @objectstack/spec patch, Clause-② no)"
],
"deviations": [
"The wording guard relayed from comment 5894445934 is adopted, in commit 02349e1. The headers describe and the advisory's message and hint now route by shape, to auth variants ConnectorInstanceAuthSchema really has: a header goes to bearer or a header api-key, a query key to api-key with paramName, a connector input to the connector's own auth.credentialRef. No text promises a url-path secret. The connectorConfig.input describe was already in that shape and is unchanged. The changeset was updated to match.",
"The os validate / os lint door pin carries the .e2e name because it spawns the CLI, so it runs in the nightly tier (the maintainer's tier direction), not per PR. The per-PR reach of those commands is pinned in lint through runAuthoringRules for each command. The save-door pin is per PR.",
"content/docs is touched only for projections. There are two reference pages (gen:docs) and four CLI transcript lines (47 to 48). The transcript lines have no generator, so each was set to the value check:docs-transcript-drift derives; no prose was touched. #20663 (the docs face) touched other files, and it merged into this branch cleanly.",
"The ablation forced the predicate false (the dispatch's alternative) instead of deleting the registry entry: deleting the entry leaves an unused import that noUnusedLocals refuses in the DTS build.",
"Two runs were cut short and re-run. First, the gate runner's timeout killed the wrapper of check:type-check-debt, whose own unlocked closure build survived as an orphan and raced the next invocation. That left two packages' dist without .d.ts; the second reading exited 3 (PREREQUISITE NOT MET), was recorded as NOT MEASURED, and the gate was re-run under the verify lock after a full rebuild: exit 0. Second, a locked job wrapped in timeout 595 was killed during the CLI unit run; it was re-run under the lock and went green.",
"Two merges of origin/main (45d004f, ae5d8af). Neither had a conflict or left an os-regen pending. The final gate union and test runs are at ae5d8af.",
"Eleven git pushes: the empty-branch probe, eight commits and two merges, each pushed before the next step that takes minutes."
],
"mcp_calls": "0 — no MCP tool was called; every read was plain curl against api.github.com",
"api_writes": "3 REST writes, each through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and opened #20698; (2) label-write --assign os-tesla, which is POST /repos//issues/20698/assignees (read back: assignee os-tesla); (3) this os-dev-report comment, which is POST /repos//issues/20654/comments via scripts/pm/post-stamped.mjs. Plus 11 git pushes, which are not REST. No label was added; the PR's size/xl, documentation, tests and tooling labels come from the labelers.",
"open_questions": [
{
"question": "Should the os validate / os lint door pin run per PR instead of nightly?",
"options": [
"A: keep it nightly (.e2e, as landed). Per-PR reach is pinned by lint's per-command runAuthoringRules test and the per-PR save-door pin.",
"B: rename it without .e2e so the integration tier runs it on every PR, at the cost of two extra CLI spawns per PR."
],
"recommendation": "A. It follows the maintainer's direction to move spawn-level end-to-end runs to nightly. The per-PR half already fails if the rule leaves any command, as the ablation showed on the lint pins."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed: pnpm check:type-check-debt (scripts/check-type-check-coverage.mjs --re-measure, refreshBuiltClosure) runs its own turbo run build --filter=./packages/* --filter=./packages/*/* outside os-verify-lock, so the gate list's lock-free check family contains a full closure build. If its wrapper is killed, the build survives as an orphan and races the next run in the same worktree. It was measured once here and left two packages' dist without .d.ts. It is tooling behaviour, not a product defect; dedupe words: type-check-debt re-measure unlocked build · refreshBuiltClosure orphan · check gate heavy build lock",
"carrier: #20590's services face (PR #20672) and the separately filed flows-guide callout correction · noted: this rule does not judge a secret in a url path, userinfo in a url, or an http node's body. A name like X-Authorization is missed by R1 unless its value carries an auth scheme. These are the ruled R1/R2 boundaries, listed in the PR's Acceptance notes, and not widened here."
]
}
ACCEPT — PR #20698 at head ae5d8af6f8 · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T18:34Z
The seat reviewed the dev report 5895978878 against GitHub and the diff. This is a security card, so the record stays abstract.
PR shape
Draft, base main, first line Fixes #20654, Clause-②: no, assignee os-tesla.
17 files (+1,220 / −9). NOT governed, and under the 5,000-line threshold.
The driver-free merge probe against origin/mainf379f57f44 exits 0. This seat re-probes against the tip before landing.
What it does (#20590's direction A, points 1 and 2, via triage 5891910265)
One exported predicate in packages/lint transcribes the measured R1/R2 rule. Its name lists are module-private, and no second copy of them exists.
One rule, flow-credential-literal, uses the predicate. It is registered once in AUTHORING_RULES at tier advisory, on validate / build / lint and the runtime publish gate for flow.
It draws one warning per credential-shaped literal in three places: an http node's headers, an http url's query keys, and a connector node's input at any depth, regions included.
It never refuses, never becomes an error and never echoes the value. A {var} template draws nothing.
The message routes by shape to the auth variants ConnectorInstanceAuthSchema actually has: a header credential to bearer or a header api-key; a query key to api-key with paramName; a connector input to the connector's own auth.credentialRef.
The HttpConfigSchema.headers and connectorConfig.input describes say a flow definition is served to every member who can read flows, and route the credential the same way. This is wording only; the reference pages are regenerated.
Nothing is withheld on any read.
Evidence (dev)
Lit control on the base: all three doors (the save door, os validate, os lint) were silent on a flow carrying four literals. The save door's advisory channel was live: a control drew flow-try-catch-without-catch.
After the change: 4 warnings with paths at os validate and at os lint, and one advisory per literal at the save door. The same flow with {var} templates, and a draft, draw nothing.
Ablation: forcing the predicate false turned 33 lint pins, the save-door pin and both CLI pins red; the restore was proven clean.
Suites passed: lint 5,433, metadata-protocol 2,786, and cli unit 3,355. The example apps draw 0 findings.
111 derived gates, all exit 0.
At-tier contract review: 5896190458 on the PR, at CONTRACT_REVIEW_TIER, on this head — PASS.
The predicate is the scanner's R1/R2, with two sound deltas: a blank draws nothing, and input arrays are walked per element.
There is no second name list in any of the 17 files.
It is advisory only, verified from each door's code, and no door-side file changed.
The wording guard 5894445934 is honoured, with no promise of a url-path secret anywhere.
Semver is right: lint minor, spec patch, Clause-②: no.
The projections are right: two regenerated reference pages, and four transcript lines moving from 47 to 48 as authoringRulesFor derives.
The seat checked its transcript: served at tier, read-only, one write (that comment).
Open question, answered by the seat: the os validate / os lint spawn pin stays nightly (.e2e), which is A. This is a verification-strategy call, not an escalation.
Per-PR coverage still fails if the rule leaves any command (lint's per-command pin), leaves the runtime surface (lint's runtime pin and the per-PR save-door pin), or if a command stops calling the registry (the wiring guard).
Only the CLI print channel is nightly-only. The review confirmed this holds.
Deviations, adopted
The wording guard applied in 02349e147e.
The CLI door pin in the nightly tier.
content/docs touched for projections only.
The ablation made by forcing the predicate false, because deleting the registry entry would leave an unused import that the DTS build refuses.
Two merges of origin/main.
Findings
check:type-check-debt runs its own closure build outside os-verify-lock, so a killed wrapper leaves an orphan build racing the next run. This is tooling, measured once here. Its carrier is none → Acceptance notes. It is filed only if it reaches CI or a second seat.
The rule does not judge a secret in a url path, userinfo in a url, or an http body. These are the ruled R1/R2 boundaries, and the PR's Acceptance notes record them.
The flows-guide callout correction is the docs face's.
Landing
When every check on this head is green or a roster skip, and the probe against the tip exits 0, this seat flips it ready and arms auto-merge. Several checks were still running at this stamp.
Merged through the merge queue at 19:06:09Z, after the ACCEPT 5896310949, the at-tier PASS 5896190458, and the seat's gated pre-landing checks on ae5d8af6f8: 35 check-runs, all green or roster skips; check-expected-skips exit 0; a driver-free probe against 0e9ad74fb4 exited 0.
Verified by content on origin/mained54768703:
16 of the 17 files are blob-identical to the reviewed head.
The httpheaders and connectorConfig.input describes say a flow definition is served to every member who can read flows. They route an outbound credential, by its shape, to a real connector auth variant.
One exported predicate draws one advisory warning on a credential-shaped literal. It fires at the save door, os validate and os lint, and never refuses.
Activity
objectstack-fleet commentedon Sep 29, 2026
Triage: route —
bug·security·priority:p1·domain:spec·area:access·pm:queue. #20590'spackages/spec+packages/linthalf, carrying its p1Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T14:04Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/spec(the two describes) andpackages/lint(the predicate and the finding) ⇒domain:spec. It is the per-layer child #20590's direction (5891721503) named, and it inherits the parent's p1.Direction: #20590's points 1 and 2, as this card states them. The emphases:
os validateandos lint. ⛔ No second name list.{var}template is not a literal. The scanner's positive-control fixture is the pin set, with template and non-credential controls that draw nothing.Clause-②: no. The describes change wording only.objectstack-fleet commentedon Sep 29, 2026
Claim: PM loop round 7
Session:
session_014EJ1ED8X4MMrT18BhVx4txAccount:
os-tesla(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20654-credential-literal-advisoryWorktree:
objectstack-issue-20654Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface: #20590's direction A, points 1 and 2 as this card carries them (
5891721503, triage5891910265), and nothing wider.packages/spec/src/automation/io-node-config.zod.ts(HttpConfigSchema.headers) and theconnectorConfig.inputdescribe inpackages/spec/src/automation/flow.zod.ts. Describe wording only: a flow definition is served to every member who can read flows, and an outbound credential goes through a declarative connector'scredentialRef. The reference docs these describes generate are regenerated only.packages/lint/src: ONE exported predicate, the scanner's R1/R2 rule (scan-open-map-credentials.mjsheader onclaude/issue-20590-open-map-credentialsat13b764d0a). It covers a credential-shaped literal in anhttpnode'sheaders, in a connector node'sinputwalked recursively, and a credential-named query key in anhttpurl, with a{var}template not a literal. Plus the one lint finding that uses it, its registration in lint's rule roster, and its tests. The scanner's positive-control fixture is the pin set, with template and non-credential controls that draw nothing.evaluateRuntimeAuthoringGate),os validateandos lint.service-automation/src/builtin/http-nodes.tsand the showcase flows), ⛔ notcontent/docs(docs(automation): the flows guide routes anhttpnode's outbound credential to a connector'scredentialRef, and replaces its secret-bearing webhook-url example (the docs half of #20590) #20655), and ⛔ notskills/(skills(objectstack-automation): the http example routes an outbound credential tocredentialRefand stops teaching a secret-bearing webhook url (the skills half of #20590) #20657).(stop on breach; explain in the report)
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tieratf4ce10c89d: no path-derived mandate, floor sonnet · default opus · ceiling fable). This is a Clause-② suspect onpackages/spec/src/**, and there is a new public export inpackages/lint, so the at-tier contract review is owed before enqueue.Clause-②: no (triage
5891910265: the describes change wording only, and the advisory never refuses; the at-tier review judges the newpackages/lintexport)Thread-read: 5891910265
Serial constraints cleared: read at 2026-09-29T14:59Z against
origin/mainf4ce10c89d, from a census of all 7 open PRs.packages/lint/,packages/spec/src/automation/orruntime-authoring-gate.ts.domain:clipackages (689 sites, 166 numbers, 95 files): the ruling C+D stage for this lane (from #20556) #20594) re-anchors comment lines inpackages/cli/src/commands/lint.tsandvalidate.ts. This card does not plan to edit either file. If a door pin needs one, it is ordinary concurrency, and the later lander merges.5891908897(domain:services), docs(automation): the flows guide routes anhttpnode's outbound credential to a connector'scredentialRef, and replaces its secret-bearing webhook-url example (the docs half of #20590) #20655 (domain:devx) and skills(objectstack-automation): the http example routes an outbound credential tocredentialRefand stops teaching a secret-bearing webhook url (the skills half of #20590) #20657 (domain:skills) each hold a disjoint file surface.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
A wording guard for this card's describes ·
domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-09-29T16:34Z · ⛔ not a claimThe at-tier record on #20590's services face (PR #20672,
5894416988, R1) found a routing clause that over-reaches. It applies to theurl/headers/inputdescribes this card writes:ConnectorInstanceAuthSchema(packages/spec/src/shared/connector-auth.zod.ts:158) has onlynone,bearer,api-key(a header, or a queryparamName) andbasic. No variant carries a secret in a url path.bearerorapi-key(header) withauth.credentialRef;api-keywithparamName;slackconnector's bot token is the in-repo example).auth.credentialRef". An author cannot follow it.content/docs/automation/flows.mdxcallout (the "Do not put a secret in an http node's url or headers" warn, about:274) carries the over-reach. ⛔ Do not copy it verbatim. It is filed for correction separately.Generated by Claude Code
docs(automation): say an http node's url and headers are served; rout…
fix(service-automation): the http node's url and headers describes ro…
objectstack-fleet commentedon Sep 29, 2026
os-dev-report
{
"issue": 20654,
"status": "done",
"branch": "claude/issue-20654-credential-literal-advisory",
"pr": "#20698",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx — this run is a subagent; the parent's id, as the container's CLAUDE_CODE_REMOTE_SESSION_ID (cse_014EJ1ED8X4MMrT18BhVx4tx) resolves",
"premise_still_valid": true,
"summary": "Draft PR #20698 (head ae5d8af, current main merged in) adds one exported predicate, isCredentialShapedLiteral (the measured R1/R2 rule, unchanged, with module-private name lists), and one advisory rule that uses it, lintFlowCredentialLiterals (flow-credential-literal). The rule is registered once in AUTHORING_RULES as tier advisory on validate / build / lint plus the runtime publish gate for flow. It reports one warning per credential-shaped literal in an http node's headers, an http url's query parameters, and a node's connectorConfig.input at any depth (regions included). It never refuses, never echoes the value, and routes by shape (header to bearer or header api-key, query key to api-key with paramName, connector input to the connector's own auth.credentialRef) in the message, which the CLI text faces print. The HttpConfigSchema.headers and connectorConfig.input describes say the definition is served to every flow reader and route the credential the same way; this is wording only, with the reference pages regenerated. Measured hypotheses: H1 confirmed on the base (all three doors silent on lit and dark; the save door's channel was live via flow-try-catch-without-catch); H2 confirmed (no door-side code change); H3: lint has no api-surface or export-origins shards, and the
.entry gains 3 values and 1 type while./runtimegains no export; H4: the 4 CLI transcript counts moved 47 to 48 (no generator exists; set to the gate-derived value) and 2 reference pages were regenerated, with no rule catalog; H5: the ablation went red and restored green as predicted.","tests": "The door readings on one flow carrying a literal in an http header, a url query key, a header inside a try region and a nested connector input (lit), and the same flow with {var} templates (dark). BASE f4ce10c: save door (evaluateRuntimeAuthoringGate, state active) lit {error:null, advisories:[]}, dark the same, control try_catch-without-catch 1 advisory flow-try-catch-without-catch; os validate --json lit/dark exit 0, valid true, warnings only 'No apps or plugins defined — this stack may not do much'; os lint --json lit/dark exit 0, passed true, issues []. AFTER: os validate lit exit 0 valid true with 4 flow-credential-literal warnings (paths flows[0].nodes[1].config.headers.Authorization, flows[0].nodes[1].config.url, flows[0].nodes[2].config.try.nodes[0].config.headers["x-api-key"], flows[0].nodes[3].connectorConfig.input.auth.clientSecret), dark none; os lint lit exit 0 passed true with the same 4 warnings, dark 0 issues; the save door pin gives lit error null with one advisory per literal, dark none, draft none; no finding carries a sentinel. FINAL HEAD ae5d8af, all under os-verify-lock: turbo build ./packages/* ./packages// 71/71; @objectstack/lint vitest 117 files / 5433 passed, typecheck exit 0; @objectstack/metadata-protocol vitest 190 passed + 3 skipped files (2786 passed / 19 skipped), typecheck exit 0; @objectstack/cli typecheck exit 0, --project unit 234 files / 3355 passed; the CLI door pin test/flow-credential-literal-doors.e2e.test.ts (nightly tier by name) run with OS_TEST_TIERS=nightly --project integration 2/2 passed; the per-PR cli integration project is declared to CI. The tier partition pin test/vitest-tiers-partition.test.ts passed 22/22. Example apps: os lint draws 0 flow-credential-literal findings on app-showcase, app-crm and app-todo. ABLATION at f7fe981 via scripts/ablation-replace.mjs WRAP: the predicate was forced false (anchor 1 to 0, blob 8a1a52e4e8a0 to f70fe5e583d4) and @objectstack/lint rebuilt; ablation-dist-preflight found the marker present in 4 built files. Lint pins: 33 red (27 positive predicate cases plus 6 rule pins) and 21 green (negatives, draws-nothing controls, registry entry, export, one-home). The save door pin went red on LIT and stayed green on DARK and draft. The CLI door pins went red on both. Restore: blob == HEAD and git diff HEAD empty (proved by the tool), the preflight with --absent found the marker absent from all 14 dist files, the tree was clean; lint 54/54, save door 3/3, CLI 2/2.",
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at ae5d8af derives 111 commands (17 paths vs merge base 05cb2bc). All 111 ran in the foreground, with each exit code captured before any pipe, and all exited 0. That includes check:docs-transcript-drift, check:generated (15/15 up to date), check:docs, check:api-surface, check:nul-bytes, check:doc-authoring, check:issue-citations, check:skill-examples and check:dual-build-cjs-loads (both exited 3, PREREQUISITE NOT MET, on an earlier pass before the closure was built, and 0 once it was), plus check:type-check-debt, run under os-verify-lock because it runs its own closure build (exit 0, 4 ledger entries, none above the record). --ran reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN. ESLint (a proven narrowing, measured at ae5d8af): the population, read from eslint.config.mjs, is /*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}. --format json over the 10 changed files in it reports 0 errors and 0 warnings. The config enables no type-aware linting (no parserOptions.project), so the diff cannot move the verdict on any untouched file. CI at report time: 9 success, 2 skipped, 21 in_progress; the required contexts are in_progress.",
"line_budget": "not applicable: no skills/ or governed surface is touched. The PR changes 1229 lines (+1220 / -9, 17 files), under the 5000 human-merge threshold.",
"files_changed": [
"packages/lint/src/credential-literal.ts (new: the one predicate)",
"packages/lint/src/credential-literal.test.ts (new)",
"packages/lint/src/lint-flow-credential-literals.ts (new: the advisory rule)",
"packages/lint/src/lint-flow-credential-literals.test.ts (new: the pin set, controls, registry and door reach)",
"packages/lint/src/authoring-rules.ts (one registry entry)",
"packages/lint/src/index.ts (exports)",
"packages/metadata-protocol/src/runtime-authoring-gate.flow-credential-literal.test.ts (new: save-door pin)",
"packages/cli/test/flow-credential-literal-doors.e2e.test.ts (new: os validate / os lint pin, nightly tier)",
"packages/spec/src/automation/io-node-config.zod.ts (headers describe and TSDoc)",
"packages/spec/src/automation/flow.zod.ts (connectorConfig.input describe and comment)",
"content/docs/references/automation/io-node-config.mdx, content/docs/references/automation/flow.mdx (regenerated by gen:docs)",
"content/docs/deployment/cli.mdx, content/docs/deployment/validating-metadata.mdx, content/docs/getting-started/build-with-claude-code.mdx, content/docs/ui/react-pages.mdx (one transcript line each: author-time rules 47 to 48, the gate-derived value)",
".changeset/20654-flow-credential-literal-advisory.md (@objectstack/lint minor, @objectstack/spec patch, Clause-② no)"
],
"deviations": [
"The wording guard relayed from comment 5894445934 is adopted, in commit 02349e1. The headers describe and the advisory's message and hint now route by shape, to auth variants ConnectorInstanceAuthSchema really has: a header goes to bearer or a header api-key, a query key to api-key with paramName, a connector input to the connector's own auth.credentialRef. No text promises a url-path secret. The connectorConfig.input describe was already in that shape and is unchanged. The changeset was updated to match.",
"The os validate / os lint door pin carries the .e2e name because it spawns the CLI, so it runs in the nightly tier (the maintainer's tier direction), not per PR. The per-PR reach of those commands is pinned in lint through runAuthoringRules for each command. The save-door pin is per PR.",
"content/docs is touched only for projections. There are two reference pages (gen:docs) and four CLI transcript lines (47 to 48). The transcript lines have no generator, so each was set to the value check:docs-transcript-drift derives; no prose was touched. #20663 (the docs face) touched other files, and it merged into this branch cleanly.",
"The ablation forced the predicate false (the dispatch's alternative) instead of deleting the registry entry: deleting the entry leaves an unused import that noUnusedLocals refuses in the DTS build.",
"Two runs were cut short and re-run. First, the gate runner's timeout killed the wrapper of check:type-check-debt, whose own unlocked closure build survived as an orphan and raced the next invocation. That left two packages' dist without .d.ts; the second reading exited 3 (PREREQUISITE NOT MET), was recorded as NOT MEASURED, and the gate was re-run under the verify lock after a full rebuild: exit 0. Second, a locked job wrapped in timeout 595 was killed during the CLI unit run; it was re-run under the lock and went green.",
"Two merges of origin/main (45d004f, ae5d8af). Neither had a conflict or left an os-regen pending. The final gate union and test runs are at ae5d8af.",
"Eleven git pushes: the empty-branch probe, eight commits and two merges, each pushed before the next step that takes minutes."
],
"mcp_calls": "0 — no MCP tool was called; every read was plain curl against api.github.com",
"api_writes": "3 REST writes, each through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and opened #20698; (2) label-write --assign os-tesla, which is POST /repos//issues/20698/assignees (read back: assignee os-tesla); (3) this os-dev-report comment, which is POST /repos//issues/20654/comments via scripts/pm/post-stamped.mjs. Plus 11 git pushes, which are not REST. No label was added; the PR's size/xl, documentation, tests and tooling labels come from the labelers.",
"open_questions": [
{
"question": "Should the os validate / os lint door pin run per PR instead of nightly?",
"options": [
"A: keep it nightly (.e2e, as landed). Per-PR reach is pinned by lint's per-command runAuthoringRules test and the per-PR save-door pin.",
"B: rename it without .e2e so the integration tier runs it on every PR, at the cost of two extra CLI spawns per PR."
],
"recommendation": "A. It follows the maintainer's direction to move spawn-level end-to-end runs to nightly. The per-PR half already fails if the rule leaves any command, as the ablation showed on the lint pins."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed:
pnpm check:type-check-debt(scripts/check-type-check-coverage.mjs --re-measure, refreshBuiltClosure) runs its ownturbo run build --filter=./packages/* --filter=./packages/*/*outside os-verify-lock, so the gate list's lock-free check family contains a full closure build. If its wrapper is killed, the build survives as an orphan and races the next run in the same worktree. It was measured once here and left two packages' dist without .d.ts. It is tooling behaviour, not a product defect; dedupe words: type-check-debt re-measure unlocked build · refreshBuiltClosure orphan · check gate heavy build lock","carrier: #20590's services face (PR #20672) and the separately filed flows-guide callout correction · noted: this rule does not judge a secret in a url path, userinfo in a url, or an http node's body. A name like X-Authorization is missed by R1 unless its value carries an auth scheme. These are the ruled R1/R2 boundaries, listed in the PR's Acceptance notes, and not widened here."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
ACCEPT — PR #20698 at head
ae5d8af6f8·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T18:34ZThe seat reviewed the dev report
5895978878against GitHub and the diff. This is asecuritycard, so the record stays abstract.PR shape
main, first lineFixes #20654,Clause-②: no, assigneeos-tesla.origin/mainf379f57f44exits 0. This seat re-probes against the tip before landing.What it does (#20590's direction A, points 1 and 2, via triage
5891910265)packages/linttranscribes the measured R1/R2 rule. Its name lists are module-private, and no second copy of them exists.flow-credential-literal, uses the predicate. It is registered once inAUTHORING_RULESat tier advisory, onvalidate/build/lintand the runtime publish gate forflow.httpnode'sheaders, anhttpurl's query keys, and a connector node'sinputat any depth, regions included.{var}template draws nothing.ConnectorInstanceAuthSchemaactually has: a header credential tobeareror a headerapi-key; a query key toapi-keywithparamName; a connector input to the connector's ownauth.credentialRef.HttpConfigSchema.headersandconnectorConfig.inputdescribes say a flow definition is served to every member who can read flows, and route the credential the same way. This is wording only; the reference pages are regenerated.Evidence (dev)
os validate,os lint) were silent on a flow carrying four literals. The save door's advisory channel was live: a control drewflow-try-catch-without-catch.os validateand atos lint, and one advisory per literal at the save door. The same flow with{var}templates, and a draft, draw nothing.At-tier contract review:
5896190458on the PR, atCONTRACT_REVIEW_TIER, on this head — PASS.5894445934is honoured, with no promise of a url-path secret anywhere.minor, specpatch,Clause-②: no.authoringRulesForderives.Open question, answered by the seat: the
os validate/os lintspawn pin stays nightly (.e2e), which is A. This is a verification-strategy call, not an escalation.Deviations, adopted
02349e147e.content/docstouched for projections only.origin/main.Findings
check:type-check-debtruns its own closure build outsideos-verify-lock, so a killed wrapper leaves an orphan build racing the next run. This is tooling, measured once here. Its carrier is none → Acceptance notes. It is filed only if it reaches CI or a second seat.httpbody. These are the ruled R1/R2 boundaries, and the PR's Acceptance notes record them.Landing
Fixes #20654closes the card.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
Landed — PR #20698 →
ed54768703; card closed ·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T19:12Z5896310949, the at-tier PASS5896190458, and the seat's gated pre-landing checks onae5d8af6f8: 35 check-runs, all green or roster skips;check-expected-skipsexit 0; a driver-free probe against0e9ad74fb4exited 0.origin/mained54768703:packages/lint/src/authoring-rules.ts, also moved onmainin between (31ed067639, PR fix(lint,metadata-protocol)!: /meta refuses a secretless api flow, and the runtime authoring gate reads a withheld-and-stored secret as present (#20611) #20692). The PR's own change to it landed unaltered: thegit patch-id --stableof the PR's diff for that file equals that of the landed diff (a00302f95c4a).Fixes #20654, and this card closedcompletedwith the merge.pm:dispatchedcomes off in this act.httpheadersandconnectorConfig.inputdescribes say a flow definition is served to every member who can read flows. They route an outbound credential, by its shape, to a real connector auth variant.os validateandos lint, and never refuses.httpdescriptor text and the showcase url) → PR fix(service-automation): the http node's url and headers describes route an outbound credential to a connector's credentialRef, and the showcase says its webhook url is served (#20590) #20672,domain:services.httpnode's outbound credential to a connector'scredentialRef, and replaces its secret-bearing webhook-url example (the docs half of #20590) #20655 and skills(objectstack-automation): the http example routes an outbound credential tocredentialRefand stops teaching a secret-bearing webhook url (the skills half of #20590) #20657.check:type-check-debt's unlocked closure build → the PR's Acceptance notes.Generated by Claude Code
docs(skills): objectstack-automation routes an outbound credential to…
feat(lint,spec): a credential typed as a literal into a served flow p…