Repository navigation
Commit 968acc8
Part of #19939
Clause-②: yes (narrowing: a {…} token is refused at http url, each
headers value, any string of a literal body, method, durable, timeoutMs
and signingSecret, and as a whole headers map, and a body value that
names a dialect must be a valid CEL value envelope; widening: the
exported FlowNodeTextSlot type gains the optional eachValue field,
LEDGER_DECLARED_NODE_CONFIG_SCHEMAS gains http, and the published
HttpConfig JSON Schema marks body as a value slot)
Pass 4, stage S5 of #19939 (stage plan `6096263424`, claim
`6122880753`), under ruling D on #11182 and ADR-0032 Decisions 2 and 3.
An `http` node reads each config key by what it is: `url` and every
`headers` value are **text slots** with `{{ }}` holes (the plan's carry
line), `body` is a **value slot** (a CEL value envelope or a literal),
and `method`, `durable`, `timeoutMs` and `signingSecret` are
**literals**, read as written (S4's seat order `6117306001`, answer B,
applied per key on the census below). A single-brace `{…}` token at any
of them is refused. The executor parses its config as authored, renders
the request once before either mode, and no longer imports the
single-brace interpolator. #19939 stays open for S6 and S7, so this PR
says `Part of`.
## The census, per key (base `9432bac07`)
Every token-bearing string at an `http` node position, in `examples/**`,
`content/docs/**` and `docs/**` (references and releases excluded),
`packages/qa/dogfood`, `packages/platform-objects` and every tracked TS
/ JS file. The test column is the fix round's structural sweep at
`1105b4a06` (each of the seven keys on an object typed `http`, ternary
branches and helper arguments followed); the first census matched http
fixtures by text and missed the ten strings marked †, rewritten in
`af1317fd1`. hotcrm is not measured (another repository; the dispatch
carries it).
| key | S5 | shipped (examples, docs, dogfood, platform-objects) | tests
| why |
|:--|:--|:--|:--|:--|
| `url` | text slot, `{{ }}` | 0 | 6:
`lint-flow-credential-literals.test.ts:269` (a template draws no
credential finding), `validate-flow-template-paths.test.ts:181`,
`flow-builtin-node-config-values.test.ts:240`,
`flow-text-slot-template.test.ts:259`, †
`runtime-authoring-gate.flow-credential-literal.test.ts:39` and †
`flow-credential-literal-doors.e2e.test.ts:71` (the DARK credential
controls) | the plan's carry line (binding): a url is text |
| `headers.*` | text slot per value, `{{ }}`; a token written as the
whole map refused | 0 | 5: `flow.test.ts:786` (`Bearer
{$Credential.token}`), `flow-builtin-node-config-values.test.ts:224`
(`headers: '{headers}'`, the build-door carve-out control), †
`runtime-authoring-gate.flow-credential-literal.test.ts:42` and †
`flow-credential-literal-doors.e2e.test.ts:74` (the DARK credential
controls), † `lint-flow-credential-literals.test.ts:84` (the scanner's
template control) | the plan's carry line (binding) |
| `body` | **value slot**, one value (the whole payload) | **5 tokens in
4 bodies**: showcase `flows/index.ts:1059` (text with a hole), `:1239`
(two whole tokens), `:1444` (one);
`content/docs/automation/flows.mdx:464` (one) | 5: †
`flow.test.ts:492`-`:493` (the approval flow, two tokens),
`flow.test.ts:788`, `flow-walk.test.ts:165`,
`validate-flow-template-paths.test.ts:560`, †
`validate-flow-template-paths.test.ts:465` (the hand-off helper, fed
`:441`, `:490`, `:500`) | measured pull |
| `method` | **literal** (B) | 0 | 0 | no flow computes it |
| `durable` | **literal** (B) | 0 | 1:
`flow-builtin-node-config-values.test.ts:223`, the build-door carve-out
control | the one test pins the interpolation itself, not a use |
| `timeoutMs` | **literal** (B) | 0 | 2: `config-parse.test.ts:193` (the
post-interpolation parse), `flow-builtin-node-config-values.test.ts:222`
(the carve-out control) | both pin the interpolation itself |
| `signingSecret` | **literal** (B), run-resolved, ⛔ not a template or
value slot | 0 | 5: `http-node-signing.test.ts:208`, `:241`, `:255`,
`:309` (a secret through a template, and the "resolved to nothing"
refusal), `guard-refusal-inventory.test.ts:160` | the tests pin the
interpolation itself; the credential channel is unchanged |
**The test, applied.** A test that exists to pin the interpolation is
not pull: it is the mechanism this card retires, the same reading S4's B
gave the designer's picker ("a surface, not a use"). So the four
zero-shipped keys are literals, and each such test is re-judged in this
PR (below).
## FROM → TO, per position
| position | you wrote | write instead | what changes |
|:--|:--|:--|:--|
| `url` | `'https://api.example.com/v1/orders/{record.id}'` |
`'https://api.example.com/v1/orders/{{ record.id }}'` | a `Date` renders
as its ISO text, not JSON-quoted |
| `headers.X` | `'r-{record.id}'` | `'r-{{ record.id }}'` | a header
that renders to no text is not sent (the template sent it empty) |
| `headers` (whole) | `'{headers}'` | the map: `{ 'X-Ref': '{{ record.id
}}' }` | a map is not computed into it |
| `body` | `{ id: '{record.id}', status: 'done' }` | `{ dialect: 'cel',
source: "{'id': record.id, 'status': dyn('done')}" }` | an absent `id`
fails the run instead of sending `null`; a literal beside a field read
is wrapped in `dyn(…)` (CEL holds a map's values to one type) |
| `body` | `{ text: 'Done: {record.title}' }` | `{ dialect: 'cel',
source: "{'text': 'Done: ' + record.title}" }` | an absent `title` fails
the run instead of sending `'Done: '` |
| `method` / `durable` / `timeoutMs` | `'{verb}'` / `'{flag}'` /
`'{timeout}'` | the value itself: `'POST'` / `true` / `5000` | a literal
now; nothing computes it |
| `signingSecret` | `'{signing_key}'` | the secret itself (a
metadata-API save keeps it in the write-only credential store), or `''`
| refused at the run |
**The one-line fix: double the braces in `url` and the `headers` values,
write `body` as one CEL value envelope that builds the payload, and
write `method`, `durable`, `timeoutMs` and `signingSecret` as the values
themselves.**
## The mechanism assumptions, measured
- **A1 holds.** `HttpConfigSchema` (`io-node-config.zod.ts:745` at the
base) carries `url`, `method`, `headers`, `body`, `durable`,
`timeoutMs`, `signingSecret` and the `surface` / `history` keys of its
`strictObject`, nothing else.
- **A2 holds, and what replaces the order.** `PARSED_AFTER_INTERPOLATION
= new Set(['http'])` existed because `http-nodes.ts:182` parsed
`interpolate(raw, …)`: the build doors judged an `http` rule only on a
config carrying no token at all. The executor no longer interpolates, so
the set had one member and loses it; the constant and its one line in
`builtinValueJudged` are deleted (a set that is always empty is a check
that can never fire). What replaces the order: credential channel →
parse the config **as authored** → render `url` and the `headers` values
→ evaluate `body` → either mode. That is the order every other
contract-carrying builtin already had (notify since S4).
- **A3 holds, with one note.** `http-nodes.ts` imports `renderTextSlot`
and `resolveValueSlot`, not `interpolate`. A literal body passes through
`resolveValueSlot`'s residual `interpolate()` (the identity on a
token-free literal; the contract parse refused any token first), the
same residual call S1–S4's slots make, which S7 deletes. The three `??`
left in the executor are the declared absent-key defaults, not tolerance
of an off-spec shape: `cfg.method ?? 'POST'` / `?? 'GET'` (the contract
documents both, and why there is no `.default()`), and `payload: body ??
{}` on the outbox arm (unchanged).
- **A4 holds, and the request is rendered once.** At the base the whole
config was interpolated in the executor before the durable branch, and
`service-messaging`'s outbox and dispatcher render nothing
(`http-outbox.ts` stores the url, headers and payload as handed; read at
the base). After S5 the executor renders `url` and the headers and
evaluates `body` once, before the durable / inline branch, so the outbox
enqueues exactly what the inline call would send — pinned
(`http-slots.test.ts` 3: the enqueued url, headers and payload are the
rendered ones, and nothing in the enqueue carries a `{{`).
## `signingSecret` — the one judgement this PR settles itself
The binding text: "`signingSecret` is a run-resolved key
(`RUN_RESOLVED_KEYS`): its secret-reference resolution stays exactly as
it is. ⛔ Do not turn it into a template or value slot." Read with A2
("once no `{var}` is read at any `http` key") and A3 ("no longer calls
`interpolate`"), the resolution that stays is the write-only credential
channel, and it does: `holdsFlowCredential` / `resolveFlowCredential`
and `RUN_RESOLVED_KEYS` are byte-identical. What goes is the `{token}`
interpolation of the secret, which the census shows only the
interpolation's own tests ever wrote. The key is therefore a literal
(S4's B), a token in it is refused with a remedy that never echoes the
value, and because the build doors leave a run-resolved key to the run,
that refusal is the executor's parse — pinned both ways (`registerFlow`
and `objectstack validate` accept `signingSecret: '{k}'`; the run
refuses it, nothing is sent). The dead "set but resolved to no value"
refusal goes with the interpolation: with nothing rendered, the parsed
secret is the authored one. Four axes: no measured business use (tests
only); one dialect and S7's resolver deletion stay possible (keeping
`interpolate` on this one key would block it); a token is refused loudly
at the run instead of signing with its braces or leaving unsigned;
immediate retirement, no window. The boundary this leaves is in
Acceptance notes.
## What lands
**The contract (`@objectstack/spec`).**
- `url` stays `z.string()` and each `headers` value `z.string()`; the
contract's refinement refuses a single-brace token in either through the
one text-slot judge (`textSlotTemplateRefusal`), with its hole spelling,
and a hole over a `$` root the engine does not bind.
- `body` takes `FlowValueSlotSchema`: an object naming a `dialect` must
be a valid CEL value envelope, and a `{…}` token in any string of a
literal is refused by the one value-slot judge, which names the envelope
that builds the whole body.
- `method`, `durable`, `timeoutMs` and `signingSecret` keep their 17.x
types; a token or an envelope there is refused with the literal's
remedy. The refusal of a token at `method`, `durable`, `timeoutMs` and
of a token written as the whole `headers` map carries
`READ_AS_WRITTEN_PARAM` (S4's mechanism, unchanged), so the build doors
report it. The refinement runs with `when: () => true`, so a token at a
typed literal (`timeoutMs: '{t}'`) gets the literal's sentence beside
the type's refusal; the build doors report the marked one, once.
- Every `describe()` of the http keys is restated, and the module
docblock.
**The ledger and the doors.**
- `FLOW_NODE_EXPRESSION_PATHS` appends the `http` `body` row (one value,
the whole payload); every older row is byte-identical (pinned).
- `FLOW_NODE_TEXT_SLOTS` gains `http.url` and `http.headers`. The
exported `FlowNodeTextSlot` gains the optional `eachValue`:
`flowNodeTextSlotSources` yields each value of the map at its own path
(`headers.X-Ref`), so `registerFlow` and `objectstack validate` judge
and compile each header like every other text slot.
- `HttpConfigSchema` joins `LEDGER_DECLARED_NODE_CONFIG_SCHEMAS`, which
carries the body's `xExpression: 'value'` to the reconciliation ratchet
(the descriptor offers `body` with no marker: one slot, one channel).
- `flow-node-config-refusals.ts`: `PARSED_AFTER_INTERPOLATION` and its
line are deleted (A2); `RUN_RESOLVED_KEYS` is unchanged.
**The executor (`@objectstack/service-automation`).** `http-nodes.ts`
parses the config as authored (after the credential channel), renders
`url` and each header through `renderTextSlot` (a url that renders to no
text refuses the node, a header that renders to no text is not sent),
evaluates `body` through the shared `resolveValueSlot`, reads the
literals as written, and no longer imports `interpolate`. The
descriptor's help strings are restated; its `type`s are unchanged.
**The lint (`@objectstack/lint`, patch).** The notify-literal hint
machinery becomes per node type (`LITERAL_KEYS` / `LITERAL_HINTS`): at
an `http` literal both wrong-syntax hints say that neither brace dialect
resolves there and name the value itself. The
`flow-double-brace-interpolation` hints name the http url and headers
among the text slots. The credential-literal docblock stops calling
`http`'s single brace its template. `objectstack validate` refuses each
position through the shared judges and the contract, with no rule
change.
**ADR-0087, D3 only.** `18.flow-value-slot-template-dialect-refused`
(surface: the http body and its four literals; replacement, reason,
acceptance), `18.flow-text-slot-single-brace-refused` and
`18.flow-text-slot-unbound-dollar-root-refused` (surface: the http url
and headers values), and `18.flow-builtin-node-config-values-refused`
with its `STEP18_RATIONALE` fragment (it said `http` parses after
interpolating and that only an `http` typed slot may take a sole-token
template; both stop being true here). No D2: an absent value in the body
wrote `null` or nothing under the template and fails the run under CEL;
a whole token at `timeoutMs` / `durable` resolved to its type and is
text now; a `Date` in a url or header rendered JSON-quoted under the
interpolator and as ISO text under the engine. No key is removed, so no
tombstone. The changeset marker is `not-required (already-registered …)`
naming the four ids.
**Sites.** The showcase's three bodies (`flows/index.ts`) and the flows
guide's http example are migrated; a literal beside a field read inside
a CEL map literal is wrapped in `dyn(…)` (measured: `{'id': record.id,
'status': 'done'}` is refused by the CEL checker at `registerFlow`, "Map
value uses wrong type"). The flows guide gains an http paragraph and the
http keys in its text-slot and value-slot lists and its dialect table;
the service-automation README's dialect table likewise. Test fixtures
that wrote the retired spellings at these keys are re-judged: in the
first pass `flow.test.ts`'s api flow, the lint credential-literal
fixtures, `config-parse.test.ts`, the signing tests, the guard-refusal
inventory and the build-door carve-out control; in the fix round
(`af1317fd1`) every remaining fixture the structural sweep found outside
the refusal pins: the two DARK credential controls (`metadata-protocol`
save door, `cli` doors; `{{ api_token }}` at the url and header, still
no advisory), the lint scanner's template header control (`{{ run_token
}}`), `flow-walk.test.ts`'s body (a CEL envelope),
`validate-flow-template-paths.test.ts`'s url pin (a `{{ }}` hole) and
hand-off payload (moved from the body, which no template reaches, to an
`X-Billing-Amount` header text slot), and `flow.test.ts`'s approval-flow
body (a CEL envelope, now parsed against `HttpConfigSchema`).
## Pins, and the ablations that turn them red
- `http-slots.test.ts` (service-automation, 21 engine pins): each
position refused at `registerFlow`, located and in its own judge's
words; the executor's contract parse refuses url, header and body tokens
and each literal's token as a guard, nothing sent; `signingSecret`
accepted at `registerFlow` and refused at the run; holes render in the
url and headers, the body envelope evaluates (`dyn`), a literal body and
a nested envelope are sent as written, a whole hole holding a map
renders JSON; the durable enqueue carries the rendered request; a url
rendering nothing refuses the node, a header rendering nothing is not
sent, an absent variable in the body fails the run with
`EXECUTION_ERROR` naming `body`.
- Spec: `io-node-config.test.ts` (24 contract pins: text slots, body,
literals with the marker, `signingSecret` without one and never echoing
the value, the build doors, the JSON projection, the describes);
`flow-node-expression-paths.test.ts` (the body row, read whole; the
census row list); `flow-text-slot-template.test.ts` (the eight slots,
`eachValue` on `headers` only, the header sources);
`flows-translation-face.test.ts` (`http.url` / `http.headers`
classified); `builtin-node-config.test.ts` (the ledger-declared map
lists `http`).
- service-automation: `config-expression-ledger.test.ts` (http declares
its one value slot on the spec Zod channel alone);
`config-parse.test.ts`, `http-node-signing.test.ts`,
`guard-refusal-inventory.test.ts` re-judged.
- lint: `validate-expressions.test.ts` (`objectstack validate` refuses
each position; `signingSecret` left to the run);
`lint-flow-patterns.test.ts` (the http literal hints, the contract's
literal keys asked of the contract; the #16405 controls move to a CRUD
`filter`, the one position S6 has not reached).
Ablations, each through `scripts/ablation-replace.mjs` (WRAP, trap on
EXIT/INT/TERM; anchor x1 → x0; restored blob == HEAD blob, `git diff
HEAD` empty):
| # | ablated | red | green after restore |
|:--|:--|:--|:--|
| 1 | the contract's url text-slot refusal | the 2 url pins | ✓ |
| 2 | the contract's header text-slot refusal | the 2 header pins | ✓ |
| 3 | the literal token refusal | the 3 literal pins + the build-door
pin | ✓ |
| 4 | the read-as-written marker on it | the same 4 (the build doors
stop reporting the token) | ✓ |
| 5 | the `signingSecret` token refusal | its pin + the run-resolved pin
| ✓ |
| 6 | the whole-`headers` token refusal | its pin + the build-door pin |
✓ |
| 7 | the ledger's body row | the 2 body-row pins + the census row list
| ✓ |
| 8 | the two `FLOW_NODE_TEXT_SLOTS` http rows, **through `dist/`**:
rebuild, `ablation-dist-preflight.mjs @objectstack/spec
'http-ablated-s5'` (marker present in 2 built files) | exactly the 4
door pins: `registerFlow` url / header (`http-slots.test.ts`),
`objectstack validate` url / header | ✓ after rebuild; preflight
`--absent` (absent from 232 built files, tree clean);
`http-slots.test.ts` 21/21 |
Ablations 1–7 ran on spec `src` (those pins import `src`); 8 is the
door, which `service-automation` and `lint` read through the spec's
`dist`.
## Verification record
Each suite through `scripts/pm/os-verify-lock.sh` (slot
`issue-19939-s5`, a shared box). `origin/main` was merged once, through
`os-regen-merge.sh` (`ca951bcb5`, from `9f4b652e3`; no path overlapped,
and the generated reference kept this branch's bytes); the last commit
after it, `1105b4a06`, is one spec test file.
- **At `af1317fd1` (the head; test files only on `1105b4a06`):**
metadata-protocol FULL "227 passed | 3 skipped (230) / 28190 passed";
lint FULL "137 / 6579"; spec local "653 / 19526 passed | 1 todo", repo
"56 / 973"; cli unit 281 + the 2 build-dependent pins after the cli
build; the cli e2e in its nightly tier "1 / 2"; typecheck exit 0 for
lint, metadata-protocol, cli, spec; the turbo-affected closure against
`9f4b652e3` (76 packages, 71 test tasks plus dogfood) all exit 0; the
120 derived gates accounted for (118 run, 2 NOT MEASURED); ESLint
narrowed 44 files, 0 / 0. Size: 50 files, +1,566 / −366 against
`9f4b652e3`.
- **At `1105b4a06`:** spec `--project local` FULL: `"653 passed (653) /
19526 passed | 1 todo"`; the gate union (below); eslint narrowed
(below).
- **At `ca951bcb5` (the merge):** the sa + lint + showcase dependency
closure built ("Tasks: 60 successful, 60 total"); `check:generated` exit
0 (14 artifacts, none stale; `check:api-surface` and
`check:authorable-surface` ✓); spec `--project repo` FULL "56 passed
(56) / 973 passed"; spec `--project local` FULL "1 failed | 652 passed
(653) / 19525 passed" — the one red was `builtin-node-config.test.ts`'s
ledger-declared key list, which this stage widens; fixed in `1105b4a06`,
and that file with `flow.test.ts` reads "250 passed" there; spec
typecheck exit 0 (test layer 52 / 246 / 135 held); the http / notify /
ledger set of service-automation "10 passed / 175 passed"; the lint set
"7 passed / 796 passed"; `@objectstack/example-showcase` FULL "33 passed
(33) / 409 passed".
- **At `bef14eda3` (before the merge; the implementation unchanged
since):** service-automation FULL "189 passed (189) / 2481 passed"; lint
FULL "137 passed (137) / 6579 passed"; spec `--project repo` FULL "56 /
973"; typecheck exit 0 for spec, service-automation (test layer 0
errors) and lint (test layer 2 / 6 / 2 held).
- **Ablations:** the table above (src at `1105b4a06`; the dist ablation
at `1105b4a06`).
- **The gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `1105b4a06` (no paths; "derived from the
tree of 'objectstack-ai/objectstack' at commit 1105b4a", the `--repo`
assertion holds, no stale-tree warning): 118 derived; 116 run, each exit
0; 2 NOT MEASURED: `pnpm check:type-check-debt` (the dispatch rules it
out) and `pnpm check:dual-build-cjs-loads` (it needs the whole
workspace's `dist`). `--ran` over the annotated record (`COMMAND :: exit
N`): "118 derived, 116 run, 2 NOT-MEASURED, 0 UNRUN", exit 0. At the
merge `ca951bcb5` the same battery had one refusal,
`check:skill-examples` exit 3 (PREREQUISITE NOT MET: the client SDK
unbuilt); after that closure was built it exits 0. The five artifact
rosters kept in a directory this diff touches each exit 0 at the head:
`check-changeset-fixed`, `check:meta-url-spelling`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`. The three PR-context guards
(`check-closing-target-claim`, `check-partof-closing-keyword`,
`check-single-claim-paths`) need this PR's number and body, so CI's runs
on this PR are their record.
- **ESLint, narrowed:** `eslint --no-inline-config --format json` over
the 40 changed `.ts` / `.mjs` files of the net diff against `9f4b652e3`,
at `1105b4a06`: exit 0; the JSON report holds 40 files, 0 errors, 0
warnings. The config enables no type-aware linting (`eslint.config.mjs`:
every `parserOptions` is `{ ecmaVersion, sourceType }`, no `project` /
`projectService`), so no untouched file's verdict can move. The
whole-tree `pnpm lint` is CI's.
- **Size:** 46 files, +1,528 / −346 (1,874 changed lines, generated
included), against `9f4b652e3`.
## Out of the claim's file surface
Each a mechanical consequence of the slots, or a stale sentence about
them:
- `flow-node-expression-paths.ts` (the body row) and
`schemaless-node-config.zod.ts` (`http` joins the ledger-declared map);
`dropped-refinements.baseline.json` (the spec build refused until
`automation/HttpConfig` was declared: sites `""` and `body`, 711 → 713).
- `18.flow-builtin-node-config-values-refused.ts` and its
`STEP18_RATIONALE` fragment (they said `http` parses after
interpolating).
- Docblocks that named the old order or the http single brace:
`flow-template-token.ts`, `builtin-node-config.zod.ts`,
`flow-value-slot-template.ts`; `parse-config.ts`, `template.ts`,
`value-slot-map.ts`, the service-automation README; lint
`credential-literal.ts`, `flow-walk.ts`,
`validate-flow-template-paths.ts`, `validate-expressions.ts`;
`service-messaging`'s `http-outbox.ts` and `plugin-webhooks`'s
`webhook-headers.ts` (comment-only, nothing published moves).
- Test fixtures and pins outside the http tests: `flow.test.ts`,
`builtin-node-config.test.ts`,
`flow-builtin-node-config-values.test.ts`,
`flows-translation-face.test.ts`, the lint credential-literal tests.
- Fixtures the fix-round sweep found outside the kit, rewritten to their
intent in `af1317fd1`: `metadata-protocol`'s
`runtime-authoring-gate.flow-credential-literal.test.ts` and `cli`'s
`flow-credential-literal-doors.e2e.test.ts` (the DARK controls, `{{
api_token }}` at the url and header), and lint's `flow-walk.test.ts`
(the body a CEL envelope) and `validate-flow-template-paths.test.ts`
(the url pin a `{{ }}` hole; the hand-off payload moved to an
`X-Billing-Amount` header).
- **In-place fixes in the same sentences (the exemption's four
conditions hold: the same stale single-brace teaching, mechanical, no
other claim on the file, the same gate family):** `template.ts` and the
flows guide still named a notify recipient as single-brace, the README's
sentence named `recipients`, and a ledger test comment counted S4's
notify rows as six (three).
## Acceptance notes
- **`signingSecret` is refused at the run, not at the build doors** —
the binding run-resolved order. So a Studio save of `signingSecret:
'{k}'` is accepted, moves into the credential store, and every run of
that node is refused (as a guard, nothing sent, the remedy naming the
secret itself). Making it a build-door refusal would mean reading the
marked token refusal ahead of `RUN_RESOLVED_KEYS`, which would also
refuse a packaged flow whose credential-store row supplies the secret
the run actually signs with; that is a contract decision for the seat,
not taken here.
- **A body envelope that evaluates to `null`** sends no body inline and
`{}` to the outbox (`payload: body ?? {}`) — the same asymmetry an
absent body had before; unchanged.
- **A CEL map literal holds one value type.** `{'id': record.id,
'status': 'done'}` is refused at `registerFlow` by the CEL checker ("Map
value uses wrong type, expected type 'dyn' but found 'string'"), loud,
and the value-slot judge's own remedy names `dyn(…)`; the docs, the
describe, the changeset and the examples write the `dyn` form.
- `validate-flow-template-paths` still path-checks a single-brace
`{record.x}` in an http body, beside the value-slot judge's refusal of
the same token; its single-brace reading goes in S7.
- The lint's http literal keys are a local set, as S4's notify set is; a
pin asks the contract which keys refuse a token as read-as-written and
holds the set to it (the contract also lists `headers`, whose whole-map
token it refuses; its values are text slots, where `{{ }}` is the
spelling).
- **NOT MEASURED:** `pnpm check:type-check-debt` (the dispatch rules it
out: `--re-measure` plus an unlocked whole-workspace build) and `pnpm
check:dual-build-cjs-loads` (it loads every published `require` entry,
so it needs the whole workspace's `dist`; CI's); hotcrm at these keys
(another repository; the dispatch carries it).
- **Carried by the seat, not here:** the skill text
(`skills/objectstack-automation/SKILL.md:236`–`:241` lists `http` among
the slots that keep the single brace) to #22585; the objectui designer's
`http` pickers to an objectui companion card.
---
_Generated by [Claude
Code](https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8c10776 commit 968acc8
50 files changed
Lines changed: 1566 additions & 366 deletions
File tree
- .changeset
- content/docs
- automation
- references/automation
- examples/app-showcase/src/automation/flows
- packages
- cli/test
- lint/src
- metadata-protocol/src
- plugins/plugin-webhooks/src
- services
- service-automation
- src
- builtin
- service-messaging/src
- spec
- src
- automation
- migrations
- entries/semantic
- system
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
235 | 235 | | |
236 | 236 | | |
237 | 237 | | |
238 | | - | |
| 238 | + | |
239 | 239 | | |
240 | 240 | | |
241 | 241 | | |
| |||
380 | 380 | | |
381 | 381 | | |
382 | 382 | | |
383 | | - | |
| 383 | + | |
| 384 | + | |
384 | 385 | | |
385 | 386 | | |
386 | 387 | | |
| |||
459 | 460 | | |
460 | 461 | | |
461 | 462 | | |
462 | | - | |
463 | | - | |
464 | | - | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
465 | 468 | | |
466 | 469 | | |
467 | 470 | | |
468 | 471 | | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
469 | 486 | | |
470 | 487 | | |
471 | 488 | | |
| |||
2090 | 2107 | | |
2091 | 2108 | | |
2092 | 2109 | | |
2093 | | - | |
2094 | | - | |
| 2110 | + | |
| 2111 | + | |
| 2112 | + | |
2095 | 2113 | | |
2096 | | - | |
2097 | | - | |
| 2114 | + | |
| 2115 | + | |
2098 | 2116 | | |
2099 | 2117 | | |
2100 | 2118 | | |
| |||
2103 | 2121 | | |
2104 | 2122 | | |
2105 | 2123 | | |
2106 | | - | |
| 2124 | + | |
2107 | 2125 | | |
2108 | 2126 | | |
2109 | 2127 | | |
| |||
2135 | 2153 | | |
2136 | 2154 | | |
2137 | 2155 | | |
2138 | | - | |
| 2156 | + | |
2139 | 2157 | | |
2140 | 2158 | | |
2141 | 2159 | | |
| |||
0 commit comments