Skip to content

feat(lint,spec): a credential typed as a literal into a served flow position draws an author-time advisory that routes it to a connector (#20654) - #20698

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-20654-credential-literal-advisory
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-20654-credential-literal-advisory

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20654
Clause-②: no

This PR is the packages/spec + packages/lint face of #20590's direction A (triage ruling 5891721503, carried by the card and restated in 5891910265). The services face (the http descriptor text and the showcase) is #20590's own claim, and the docs and skill faces are #20655 and #20657. #20590 is not closed by this PR.

What changes

A flow definition is served, as authored, to every member who can read flows. The read path withholds the credential slots the spec declares, but an open map or a url cannot be withheld: an ordinary value there is indistinguishable from a credential. So this PR steers and warns. Nothing is withheld and nothing is refused.

  • One predicate, one home. @objectstack/lint gains isCredentialShapedLiteral(name, value) in packages/lint/src/credential-literal.ts. It is the measured scanner's R1/R2 rule, unchanged: a credential-named key (R1) or an auth-scheme value (R2), on a non-blank string that carries no {…} template. The name lists are module-private. A test pins that no other shipped source file in the package holds them.
  • One advisory. lintFlowCredentialLiterals (rule id flow-credential-literal, packages/lint/src/lint-flow-credential-literals.ts) reports one warning per credential-shaped literal in three positions: an http node's config.headers entry, a query parameter of an http node's config.url, and a node's connectorConfig.input at any depth. Nodes nested in try_catch / loop / parallel regions are included. The finding names the key and the position, and it never carries the value.
  • Routed by shape. A header credential goes to a connector whose auth is bearer or a header api-key. A query-string key goes to api-key with paramName. A connector input goes to the connector's own auth.credentialRef. The route is in the message, which is what the os validate and os lint text faces print; the hint carries the full declaration. No text promises a variant that carries a secret in a url path. This follows the wording guard the services seat relayed in 5894445934.
  • Registered once. There is one new AUTHORING_RULES entry, tier: 'advisory', on all three commands, with surfaces: ['cli', 'runtime-publish'] and runtimeTypes: ['flow']. The wiring guard's "every advisory rule really is advisory" check reads the rule's source for an error severity. The finding type is severity: 'warning'.
  • Describes. HttpConfigSchema.headers and a flow node's connectorConfig.input say the definition is served to every flow reader, and route an outbound credential as above. The wording changes; no shape changes. content/docs/references/automation/{flow,io-node-config}.mdx are regenerated with gen:docs.
  • Changeset. @objectstack/lint minor (new exports and a new advisory), @objectstack/spec patch (describe text only). Both packages' dist carry the change: measured by grep of files[] after the build, with a positive control.

Mechanism hypotheses, measured

  1. Lit control on the base (f4ce10c89d): confirmed. A flow with a credential-shaped literal in an http header, a url query key and a nested connector input got these answers:
    • The save door (evaluateRuntimeAuthoringGate, state: 'active') returned error: null, advisories: []. The dark control (the same flow with {var} templates) returned the same.
    • The live-channel control, a try_catch with no catch, drew exactly one advisory, flow-try-catch-without-catch.
    • os validate --json exited 0 with valid: true. Its only warning was No apps or plugins defined — this stack may not do much, lit and dark alike.
    • os lint --json exited 0 with passed: true and issues: [], lit and dark alike.
  2. No door-side change: confirmed. The registry entry alone reaches all three doors. packages/cli/src/commands/* and runtime-authoring-gate.ts are untouched.
  3. Published surface. packages/lint has no api-surface or export-origins shards. The . entry gains isCredentialShapedLiteral, lintFlowCredentialLiterals, FLOW_CREDENTIAL_LITERAL and the type FlowCredentialLiteralFinding. ./runtime exports nothing new.
  4. Derived artifacts moved.
    • check:docs-transcript-drift: the four CLI transcripts print Running author-time rules (48), up from 47. There is no generator; each line is set to the value the gate derives from authoringRulesFor(cmd).
    • check:docs: the two reference pages above, via gen:docs.
    • No rule catalog exists.
    • check:generated reports all 15 artifacts up to date.
  5. Ablation: confirmed, in the predicted direction. The ablation ran at f7fe981913 through scripts/ablation-replace.mjs (WRAP mode). It made the predicate always return false, then rebuilt @objectstack/lint.
    • The dist preflight found the marker in 4 built files.
    • The lint pins went red on 33 tests: all 27 positive predicate cases and 6 rule pins (the pin set, severity, wording, array walk, reach through runAuthoringRules for each command, reach through runRuntimeAuthoringRules). The negatives, the draws-nothing controls, the registry entry, the export and the one-home pin stayed green, 21 in all.
    • The save-door pin went red on LIT and stayed green on DARK and draft. The os validate and os lint pins both went red.
    • Restore: the blob matched HEAD, git diff HEAD was empty, the marker was absent from all 14 dist files and the tree was clean. Then lint 54/54, save door 3/3 and CLI doors 2/2 all passed.

Verification record (HEAD ae5d8af6f8, which includes current main)

  • The door readings after the change, on the same lit and dark fixtures:
    • os validate exits 0 with valid: true. Lit prints four flow-credential-literal warnings: the header, the url query key, the header inside the try region and the nested connector input. Dark prints none.
    • os lint exits 0 with passed: true. Lit reports the same four warnings; dark reports none.
    • The save door returns error: null. Lit carries one advisory per literal on the advisory channel; dark and draft carry none.
    • No finding carries a sentinel value.
  • The pins:
    • packages/lint: the pin set is the measured scanner's positive-control fixture (literal arm, doc-block arm, probe flow), carried over as the evaluated stack. It draws exactly 12 advisories, and the template, ordinary-value and signingSecret controls draw none.
    • The save door: packages/metadata-protocol/src/runtime-authoring-gate.flow-credential-literal.test.ts.
    • os validate / os lint: packages/cli/test/flow-credential-literal-doors.e2e.test.ts. It spawns the CLI, so it sits in the nightly tier by name; the per-PR half is the per-command runAuthoringRules pin in lint.
  • Tests:
    • @objectstack/lint: 117 files, 5433 tests passed, typecheck exit 0.
    • @objectstack/metadata-protocol: 190 files passed and 3 skipped (2786 tests passed, 19 skipped), typecheck exit 0.
    • @objectstack/cli: unit project 234 files, 3355 tests passed, typecheck exit 0. The door pin passes 2/2 under OS_TEST_TIERS=nightly. The per-PR integration project is left to CI.
  • Example apps: os lint draws 0 flow-credential-literal findings on app-showcase, app-crm and app-todo.
  • Gates:
    • dispatch-gates --commands derives 111 gates at ae5d8af6f8. All 111 ran with their exit codes captured before any pipe, and all exited 0.
    • --ran reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN.
    • check:type-check-debt ran under the verify lock, because it runs its own closure build.
  • ESLint (a proven narrowing): the config's population 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, so this diff cannot move the verdict on any untouched file.

Acceptance notes


Generated by Claude Code

…a literal into a served flow position

- @objectstack/lint gains one exported predicate, isCredentialShapedLiteral,
  the single home of the name/value rule, and one advisory rule that uses it,
  lintFlowCredentialLiterals (flow-credential-literal), registered tier
  advisory on os validate / os build / os lint and the runtime publish gate
  for flow writes. It never refuses and never echoes the value.
- The spec describes of an http node's headers and a connector node's input
  say the definition is served to every flow reader and route an outbound
  credential to a declarative connector's auth.credentialRef.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…he save door, os validate and os lint

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…ges from the new describes

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…egistry now derives (48)

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…in its message, which the CLI text faces print

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…e connector schema really has

A header credential goes to a bearer or header api-key connector auth, a
query-string key to api-key with paramName, a connector input to the
connector's own credentialRef. No text promises a variant that carries a
secret in a url path.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 28 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object), runtimeTypes (symbol, a field of const object AUTHORING_RULES))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 14f80e23957165f6fb23c2b3d59bdc7668652dfb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 76f8f6f8d2c82245193675d6cf1b6d4cae031f28 — the merge of head ae5d8af6f81050dcf56c8893698df875d2f9839d into base 14f80e23957165f6fb23c2b3d59bdc7668652dfb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 76f8f6f8d2c82245193675d6cf1b6d4cae031f28 && git checkout 76f8f6f8d2c82245193675d6cf1b6d4cae031f28
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 14f80e23957165f6fb23c2b3d59bdc7668652dfb ae5d8af6f81050dcf56c8893698df875d2f9839d && git checkout -B drift-repro 14f80e23957165f6fb23c2b3d59bdc7668652dfb && git merge --no-ff ae5d8af6f81050dcf56c8893698df875d2f9839d

node scripts/docs-audit/affected-docs.mjs --json 14f80e23957165f6fb23c2b3d59bdc7668652dfb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 14f80e23957165f6fb23c2b3d59bdc7668652dfb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ae5d8af6f81050dcf56c8893698df875d2f9839d
Local-runs: none

Inputs read: card #20654 (body, triage 5891910265, claim 5892848874, wording guard 5894445934, the os-dev-report), parent #20590 (5890702006, 5891721503), the scanner header at 13b764d0a, PR #20698 (body, its one bot comment, 17-file list, the net diff against main from merge-base 05cb2bc03, which equals the file list), and the 32 check-runs on the head, read once. Security card: this record describes the class and the steer and prints no credential shape beyond the fixtures' sentinel placeholders.

① Derived judgments

  • One predicate, one home — right. isCredentialShapedLiteral(name, value) in packages/lint/src/credential-literal.ts is the scanner's R1/R2 transcribed: the same three full names, the same 11 name segments, the same 4 adjacent pairs, the same camelCase and _-to-- split, the same auth-scheme value regex, the same {…} template token (http-nodes interpolation). Two deltas, both stated in the PR body and both right for an advisory over an evaluated stack: a blank string draws nothing (the scanner counted it), and a connector-input array is walked element by element under its parent key (the scanner classed an array as computed). The name lists are module-private; a grep of all 17 changed files finds the list tokens only in the predicate's own test, as inputs it judges and as the one-home pin's search strings. packages/lint/src is flat, so that pin's non-recursive scan reaches every shipped source file. No second list anywhere in the diff — rule, tests, CLI, metadata-protocol, changeset.
  • The url arm — right, with its boundary named. The query-key arm applies the same predicate to each decoded name=value pair of an http node's config.url. The scanner's separate URL_SHAPE regex (userinfo, extra key names, webhook hosts) is not carried over: it was reported beside the counts and never folded in, a second list is forbidden, and triage's text is "a credential-named query key". So a secret in a url path, userinfo, an http body, and a key whose name the list does not know are outside the rule. The PR's Acceptance notes say so; nothing is widened silently.
  • Advisory only — right, from the code at every door. The registry entry is tier: 'advisory', commands: ALL, surfaces: ['cli', 'runtime-publish'], runtimeTypes: ['flow'], one entry. The finding type pins severity: 'warning'; the rule body emits no error, which authoring-rule-wiring.test.ts (invariant 3) verifies by reading the source. splitBySeverity and runRuntimeAuthoringRules route by severity, so the finding can only land in advisories. evaluateRuntimeAuthoringGate returns error: null when errors and its local issues are empty, and drafts are not judged (D1). os validate and os lint fail a run on error only; --strict promotes every warning uniformly, as before. No door can refuse or block on this rule, and no path in the diff touches a read projection: nothing is withheld.
  • The value is never echoed — right. A finding carries where (flow, region trail, node id and type), path (the config path; for the url arm …config.url, not the url), the key NAME and the route. The save door's deduped log line prints message and hint only. The pin sets assert no sentinel reaches the serialised findings at the rule, the save door and both CLI doors.
  • Three doors with no door-side change — right. packages/cli/src/commands/* and runtime-authoring-gate.ts are untouched. validate.ts and compile.ts (the build command) call runAuthoringRules(cmd) and print authoringRulesFor(cmd).length; lint.ts maps each finding to a LintIssue; both text faces print where: message, and the route sits in the message, so the terminal reader gets it without --json. The runtime gate selects by runtimeAuthoringRulesFor('flow'). flow-walk.ts (walkFlowNodes with regionTrail) and object-graph.ts (recordsOf) exist at the head; content/docs/automation/connectors.mdx (cited by the hint) and ADR-0097 §3 exist at the head.
  • Wording guard 5894445934 — right, on every read. ConnectorInstanceAuthSchema (connector-auth.zod.ts:158) has none, bearer, api-key (headerName or paramName) and basic (username + credentialRef). The headers describe routes to bearer or a header api-key with auth.credentialRef; the advisory's message routes a header the same way, a query key to api-key with paramName, a connector input to the connector's own auth.credentialRef; the hints name those declarations and the basic pair. The connectorConfig.input describe was already in that shape. No text in the describes, the messages, the hints or the changeset promises a url-path secret a route; the rule test asserts no finding text mentions a webhook. The url describe is unchanged, which is within the card's deliverable (two describes).
  • Public surface — right. @objectstack/lint's . entry gains isCredentialShapedLiteral, lintFlowCredentialLiterals, FLOW_CREDENTIAL_LITERAL and the type FlowCredentialLiteralFinding; ./runtime gains nothing. Api-surface and export-origins shards exist for packages/spec only, and spec's shard carries no describe text, so no shard moves. @objectstack/spec changes two describe strings and two comments; no zod shape changes, so the accept-set is unchanged.
  • Projections — right. content/docs/references/automation/{flow,io-node-config}.mdx table cells equal the new describe strings. The four transcript lines move 47 → 48 on three os validate pages and one os build page (cli.mdx under ◆ Compile); counting commands: entries in the registry source text gives validate 47 → 48, build 47 → 48, lint 44 → 45, which is what check-docs-transcript-drift.mjs derives through authoringRulesFor(cmd).length. No generated rule catalog exists (the sibling rule ids appear outside packages/lint/src only in prose, changelogs and migrations). Nothing else under content/docs is touched; automation/flows.mdx (docs(automation): the flows guide routes an http node's outbound credential to a connector's credentialRef, and replaces its secret-bearing webhook-url example (the docs half of #20590) #20655) is untouched.
  • File surface — within the claim. The 17 files are the claim's surface exactly; [security] closeout: two stored-credential positions outside #20552's projection still reach a served read unredacted (a flow http node's signingSecret; the /meta list read's raw fallback) #20590's services face, content/docs prose and skills/ are untouched. No governed surface is in the file list.

② Semver level

  • .changeset/20654-flow-credential-literal-advisory.md: @objectstack/lint minor, @objectstack/spec patch — right. Lint publishes new exports and a new advisory rule (additive; every prior advisory landed the same way, and --strict promotion is the flag's standing semantics, not a new contract). Spec publishes wording only. @objectstack/cli and @objectstack/metadata-protocol gain test files only and publish nothing, so no changeset is owed for them. Check Changeset concluded success on this head.
  • Clause-②: no — right. Both the PR body and the changeset carry it; no accept-set widens or narrows (the describes are text, the schemas are byte-unchanged in shape), and the advisory never refuses. Matches triage 5891910265 and the parent's 5891721503.

③ Boundary flags

  • Wording guard adopted (deviation 1) — answered above: verified against the schema's variants; right.
  • Open question: CLI door pin nightly (A) vs per-PR (B) — the seat ruled A; judged here for whether per-PR coverage still fails if the rule leaves any door. *.e2e.test.* is the nightly tier by name and Test Core runs with OS_TEST_TIERS: queue, so flow-credential-literal-doors.e2e.test.ts is not per-PR. Per PR: if the entry leaves any command, lint's reaches the CLI commands through the registry pin (iterating AUTHORING_COMMANDS against the 12-path pin set) and its registry-entry pin fail; if it leaves runtime-publish or flow, lint's runtime-gate pin and packages/metadata-protocol's save-door pin fail; if a command file stops calling the registry, the wiring guard fails. Only the CLI print channel itself is nightly-only. A holds; no escalation.
  • content/docs touched for projections only (deviation 3) — answered above; right.
  • Ablation shape (deviation 4), the two cut-short re-runs (deviation 5), two main merges (deviation 6), eleven pushes (deviation 7) — process notes on the dev's run, not properties of the diff; the net diff from merge-base is the 17 files and nothing else. No action.
  • Out-of-scope note 1 (check:type-check-debt builds outside the verify lock) — tooling behaviour, not this diff's; left as the dev filed it, noted not filed.
  • Out-of-scope note 2 (url path, userinfo, body, an X-Authorization-style name) — the ruled R1/R2 boundaries, listed in the PR; not widened here and not owed by this card.
  • Gate coverage on this head (32 check-runs, read once, 0 red): concluded green — Build Core, Build Docs, Check Changeset, Check Documentation Links, Check PR Size, Governed Surface Queue Guard, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, The card this PR closes must claim this branch, Type Check · debt ledger, Type Check · source gates, Auto Label, filter, Flag docs affected by code changes; skipped — Console Pin Gate, Packed-tarball smoke (opt-in). Not concluded (14), named rather than presumed green: Lint & Repo Gates (carries check:docs-transcript-drift, check:docs, check:api-surface, check:generated --reconcile-only, check:cross-package-test-inputs, pnpm lint, check:type-check-debt), Test Core 1/6 – 6/6 (carries the lint, metadata-protocol and cli queue-tier vitest, i.e. the per-PR pins above), Type Check · workspace, Type Check · consumer gates, Dogfood Regression Gate 1/3 – 3/3, Dogfood Verify CLI, Temporal Conformance (live PG + MySQL). The verdict below is on the diff's contract; the landing still waits on those concluding green, per the rule that every check is green.

Implemented-by: claude/issue-20654-credential-literal-advisory
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 18:36
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit ed54768 Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20654-credential-literal-advisory branch September 29, 2026 19:06
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…#20682) (objectstack-ai#20704)

Fixes objectstack-ai#20682

Clause-②: no

Docs-only, `content/docs/automation/flows.mdx`, two spots.

1. The `http` node callout no longer sends every credential to a
declarative connector's `auth.credentialRef`. It routes by shape: a
header credential to `bearer`/`basic`/`api-key`; a query key to
`api-key` with `paramName`; a secret in the url path has no
`credentialRef` variant, so it goes to a token-authenticated connector
such as `slack`, whose bot token is supplied by host code (the Slack
connector is plugin-registered, not a declarative `connectors:`
provider; only `rest`, `openapi`, `mcp` register providers). The first
sentence and the `signingSecret` / start-node `secret` sentence are
unchanged.
2. The retired-shapes row for `actionType: 'slack'` keeps the
`connector_action` + Slack connector and drops the incoming-webhook
`http` alternative.

Wording is in step with objectstack-ai#20672's `http` descriptor and objectstack-ai#20698's
describes.

## Acceptance notes

- `pnpm --filter @objectstack/spec run check:skill-examples` (client
dist not built) and `check:gitlink-declared` were not run: NOT MEASURED.
The other derived gates ran green (33 of 40 derived; the rest are
checker self-tests), `doc-security-posture` and `docs-spec-enumerations`
included.
- No changeset: docs-only.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants