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
[Decision] flows and records: may a flow start input declare a record reference (A), and does a non-system create stop choosing the new record's id (C)? #29425
Ruled: 6580257050 · letter A+C · 2026-11-02T14:56Z
Filing gate: ② a decision only the maintainer can make. It is a follow-up to #29050 (closed) and PR #29365 (merged dfbe34d887), handled under ruling C. This card states rules only. The seat holds the reasoning in the project and gives it to the maintainer there, not here. Filed by domain:server seat 5. ⛔ Not a claim. Suggested grade: p2.
Maintainer quick-read
PR #29365 made every door that starts a flow by name check a start input that the flow definition visibly reads as a record id. The check loads the record with the caller's own permissions before the run exists. It covers only inputs the definition reads that way in plain sight. Two further rules would make the check complete. Each one is a product choice, so the seat does not pick for you.
Options
Option
The rule
Who sees it
Work
A — declared record reference
A flow start input may declare "this is the id of a record of object X". Every start door then checks a declared input the same way #29365 checks a visible read: loaded with the caller's permissions, before the run exists.
Flow authors gain one optional key. Flows that don't use it behave as today.
domain:spec key first (1 PR), then domain:server doors (1 PR). Precedent: PR #29365, 967 lines in 8 files.
C — engine-chosen ids on create
When a non-system caller creates a record, the platform chooses the new record's id. A caller-supplied id is refused with 400 naming id. System callers (import, migrate, seed, platform services) keep choosing ids.
API and SDK clients that send their own id on create start failing loudly. This is BREAKING (narrowing). Not yet measured: which shipped clients, connectors or examples send an id today. The PR measures that first.
domain:server · 1 PR · full dogfood · no landed precedent
A + C (seat recommends)
Both.
Both of the above.
3 PRs, spec first.
Neither
Keep #29365's rule as shipped: only visibly read inputs are checked.
Nobody.
None.
Why A + C: A extends the shipped rule to every input an author declares. C removes a whole class of question at the source, rather than checking each consumer. Both are rules a reader can state in one sentence.
Protocol: A adds a key (Clause-②: yes (widening), Metadata-protocol: added, spec lane). C narrows an accept set (Clause-②: no (narrowing), BREAKING, ADR-0087 marker).
State: needs-user-decision. The seat dispatches the ruled letter privately under ruling C. Public PRs state the rule only.
C — engine-chosen ids on create. When a non-system caller creates a record, the platform chooses the id; a caller-supplied id is refused 400 naming id. "System caller" means the system identity only (runAs: system, import, migrate, seed, platform services). An administrator account is a non-system caller and is refused the same way. The refusal names the alternative: upsert by a declared external key.
C: domain:server one PR, BREAKING banner, ADR-0087 marker, full dogfood. Before it lands, its body states the measurement the card promised: which shipped clients, connectors, examples and Console paths send an id on create today. If that measurement finds a shipped caller sending ids, those callers move first and C lands after them.
Execution
Server seat 5 dispatches under rule C as the card says: public surfaces state the rule only.
Confidence gaps carried from the brief: the private security reasoning behind C was not read by this seat; the population of id-sending clients is unmeasured until the C PR measures it.
C: claimed below, under ruling C. Public text states the rule only.
Claim: PM loop · card thread · letter C (engine-chosen ids on create) · 2026-11-02T15:04Z
Branch: pending (the card thread is being opened; a "branch named" claim supersedes this one).
Clause-②: no (narrowing)
Metadata-protocol: none
Order: measure first, and put the measurement in the PR body: which shipped clients, connectors, examples and Console paths send an id on create today. If any shipped caller does, stop and report; those callers move first. Then: a non-system caller's create carrying an id is refused with 400 naming id, and the refusal names the alternative (upsert by a declared external key). The system identity only (runAs: system, import, migrate, seed, platform services) keeps choosing ids, and an administrator account is refused like any non-system caller. One PR, Refs #29425, with a BREAKING banner, an ADR-0087 marker and full dogfood run locally.
{
"issue": 29425,
"status": "needs_decision",
"branch": "claude/project-thread-li91rx",
"pr": null,
"session": "session_01ADHkmQ6MkQtwGFr2gAD9qS",
"premise_still_valid": true,
"summary": "Letter C, step 1 (measure first) on origin/main a6441636, static reading, no code written. Three stop conditions hold. (1) A shipped caller sends an id on create in a non-system context: the shared import runner (packages/core/src/utils/import-runner.ts, flushPendingCreates) gives every buffered create row a minted id for its retry recheck and keeps a file-supplied id, and writes as the requester; it serves POST /data/:object/import, the async import jobs, the Console Import wizard and the SDK import. Clone overrides.id and the batch upsert create arm also create with a caller id. (2) ADR-0120's amendment of 2026-11-01 records the opposite rule ('Every create door accepts a caller-supplied id') and lists this exact refusal as rejected option a, with a bar for closing it; two dogfood files and http-protocol.mdx pin that contract, so C needs a superseding ADR record (Tier H). (3) 'Upsert by a declared external key' does not exist for an API caller: the batch upsert declares externalId but keys on id only, and the field externalId flag is described as not read; the only key-matched upsert a non-system caller has is the import door (writeMode 'upsert' + matchFields), which is caller (1). Detail: /mnt/project-files/server-seat5/29425-c-measurement.md (project files).",
"tests": "none — read-only measurement, no diff",
"gates": "n/a — no diff, no commit",
"dogfood_battery": "n/a — no diff; owed once on the final head of the C PR",
"mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #29425; no write tool",
"api_writes": "1 — this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/29425/comments, through the fleet relay)",
"open_questions": [
{
"question": "Ruling C contradicts ADR-0120's 2026-11-01 amendment, and a shipped caller (the import runner) creates with an id as the requester. How do the callers move first, and does C supersede the amendment?",
"options": [
"A: C stands. A superseding ADR-0120 amendment (Tier H, draft) lands with the code. The import runner moves first, server-only: its own minted ids are admitted through an in-process platform mark that no request can carry, and a file-supplied id on a create row is refused per row with 400 naming id. Existing-row round trips keep working, because an update matched on id is not a create. Clone overrides.id and the batch upsert create arm are refused too, so a by-id upsert becomes: update a readable row, otherwise 400. The refusal names the import door's upsert (writeMode 'upsert' + matchFields) as the alternative. Work: ADR amendment PR plus one server PR (objectql insert door, core import runner, forced dogfood edits, http-protocol.mdx text). BREAKING, no landed precedent.",
"B: A, plus a real external-key upsert on the API. The batch upsert reads its declared externalId against a declared key field, so the refusal names a door on the same surface. Adds a spec-lane card (the field flag's meaning) and a server PR, landed before the C door.",
"C0: Withdraw C and keep ADR-0120 as amended (caller-supplied ids accepted, a collision answers 409). Zero PRs on this letter."
],
"recommendation": "B. Two years out, the identity model the ruling describes is engine-chosen ids plus a declared external-key upsert on every API door (the Salesforce model; Dataverse and ServiceNow, which the ADR cites, accept caller ids). B is the only option whose named alternative sits on the same surface the caller was refused on; under A, an API client keyed by its own ids has no API path left. Order: the ADR-0120 amendment and the spec-lane card for the external-key upsert first, then the server PR that makes the batch upsert read its key, then the C PR (import runner moves, insert door refuses). Confidence gaps: the run-time census of exercised create paths was not run (stopped at step 1), and the carrier of the importer mark (engine option vs row registry) is unmeasured."
},
{
"question": "Does 'system caller' include a context-less engine insert (no principal, no isSystem)?",
"options": [
"A: No. The ruling's 'system identity only' is read literally: only isSystem: true passes, and a context-less insert carrying an id is refused like a member's, in line with ADR-0096 D5's fail-closed direction.",
"B: Yes, for the transition. Context-less calls keep their id until ADR-0096 D5 flips, and the census lists them."
],
"recommendation": "A. It is the ruling's own wording and ADR-0096's direction. The C PR runs the census first and moves any context-less id-sender to an explicit system context in the same PR."
}
],
"security_findings": "withheld",
"out_of_scope_findings": [
"carrier: this card's decision (open question 1, option B) · the batch upsert's declared externalId row key is never read by the handler (spec:BatchRecordSchema.externalId -> consumer: none); NOT MEASURED at a public door · noted, not filed"
],
"files_changed": [],
"line_budget": "n/a — no diff",
"deviations": [
"Stopped at step 1 under the delta's stop rule: no edit, no commit, no PR, no changeset.",
"NOT MEASURED: a run-time census of non-system creates carrying an id over the dogfood battery. Reason: the static reading met the stop rule, and the ADR conflict puts the design with the maintainer. The census is the first step of the C PR once a letter is ruled.",
"File surface reported, not claimed: if A or B proceeds, it reaches beyond the delta's expected surface. It adds packages/core (the import runner), docs/adr/0120 (governed, Tier H) and content/docs/protocol/kernel/http-protocol.mdx. B also needs a packages/spec card first.",
"The dogfood closure build was started under the verify lock ahead of the implementation phase. It is a local build only and writes nothing to the repository."
]
}
Needs decision · #29425 letter C · 2026-11-02T15:23Z
Ruling 6580257050 told the C PR to measure first, and to move any shipped caller that sends an id on create before C lands. The dev's measurement (report 6580899246, static reading on maina6441636) found three stop conditions, so letter C returns to the maintainer. Letter A is unaffected: spec #29442 proceeds.
What stopped it (read in the report and spot-checked by the seat in the ADR text):
A shipped caller. The shared import runner creates rows with an id as the requester. That covers POST /data/:object/import, import jobs, the Console Import wizard and the SDK import. Clone (overrides.id) and the batch-upsert create arm do the same.
An accepted ADR says the opposite. ADR-0120's amendment of 2026-11-01 (maintainer 「同意」) states that every create door accepts a caller-supplied id, and it lists this exact refusal as a rejected option, with a bar for reopening it: import round trips must keep working without an exemption that reopens the door. Two dogfood files and http-protocol.mdx pin that contract. C would need a superseding ADR amendment (Tier H).
The named alternative doesn't exist on the API. The batch upsert declares externalId but keys on id only, and the field-level externalId flag is described as not read. The only key-matched upsert a non-system caller has is the import door, which is caller 1.
Options (maintainer):
Option
What it does
Who sees it
Work
C0 — withdraw C (seat recommends)
Keep ADR-0120 as amended: a caller-supplied id is accepted, and a collision answers 409. Letter A still lands.
Nobody.
None on this letter
A′ — C with the ADR amendment
A superseding ADR-0120 amendment. The import runner moves first: its own ids carry an in-process platform mark, and a file id on a create row is refused per row. Clone and the by-id upsert create arm are refused. The refusal names the import upsert as the alternative.
Import files that carry ids for new rows, clone, and API clients keyed by their own ids.
ADR PR (Tier H) + 1 server PR, BREAKING, no precedent
B′ — A′ plus an API external-key upsert first (the dev recommends this)
The batch upsert reads a declared external key, so the refusal names a door on the same surface.
Same as A′, but API clients keep a keyed path.
Spec card + 2 server PRs + ADR PR
Why the seat recommends C0: the maintainer amended ADR-0120 to accept caller ids one day before this ruling, against the same bar the ruling now meets. Letter A plus the shipped rule (#29365) already judge every input a flow reads as a record, as the caller, so C is defence in depth rather than the fix. A′ and B′ break import, clone and by-id upsert for a gain the flow-door rule already covers. If the long-term identity model should be engine-chosen ids, B′ is the honest shape, but as its own ADR-level project, not a follow-up to this card.
A second question is moot under C0. Under A′ or B′: does a context-less engine insert (no principal, not isSystem) count as a system caller? The dev and the seat both say no, read literally, which is ADR-0096 D5's fail-closed direction.
State: pm:dispatched → needs-user-decision. The card thread stays parked on claude/project-thread-li91rx with no diff.
Activity
zhuangjianguo commentedon Nov 2, 2026
Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #458 · 2026-11-02T14:50Z
Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
Ruling: batch #458 item 1 · letter A+C · maintainer 「同意」 2026-11-02T14:54Z
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
What the ruling binds
404 RECORD_NOT_FOUND,400 VALIDATION_FAILEDnaming the input). An input without the declaration keeps fix(runtime): every flow start door judges a start input against the object the flow's definition reads it from #29365's visible-read rule. The key's spelling is the spec card's to set (the brief suggeststype: 'record'beside anobjectName, the word the CRUD nodes already use).idis refused400namingid. "System caller" means the system identity only (runAs: system, import, migrate, seed, platform services). An administrator account is a non-system caller and is refused the same way. The refusal names the alternative: upsert by a declared external key.Workload of the letter
domain:specone PR (the key;Clause-②: yes (widening),Metadata-protocol: added), thendomain:serverone PR Blocked-by it (every start door checks a declared input; precedent PR fix(runtime): every flow start door judges a start input against the object the flow's definition reads it from #29365, 967 lines / 8 files).domain:serverone PR, BREAKING banner, ADR-0087 marker, full dogfood. Before it lands, its body states the measurement the card promised: which shipped clients, connectors, examples and Console paths send anidon create today. If that measurement finds a shipped caller sending ids, those callers move first and C lands after them.Execution
Confidence gaps carried from the brief: the private security reasoning behind C was not read by this seat; the population of id-sending clients is unmeasured until the C PR measures it.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Ruling A+C (6580257050) received. Executed in order:
Claim: PM loop · card thread · letter C (engine-chosen ids on create) · 2026-11-02T15:04Z
Branch: pending (the card thread is being opened; a "branch named" claim supersedes this one).
Clause-②: no (narrowing)
Metadata-protocol: none
Order: measure first, and put the measurement in the PR body: which shipped clients, connectors, examples and Console paths send an
idon create today. If any shipped caller does, stop and report; those callers move first. Then: a non-system caller's create carrying anidis refused with400namingid, and the refusal names the alternative (upsert by a declared external key). The system identity only (runAs: system, import, migrate, seed, platform services) keeps choosing ids, and an administrator account is refused like any non-system caller. One PR,Refs #29425, with a BREAKING banner, an ADR-0087 marker and full dogfood run locally.State:
needs-user-decision→pm:dispatched.objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Claim: PM loop · card thread · letter C (engine-chosen ids on create) · branch named · 2026-11-02T15:08Z (supersedes the pending-branch claim above)
Seat: domain:server#5 · branch
claude/project-thread-li91rx· session_01ADHkmQ6MkQtwGFr2gAD9qSClause-②: no (narrowing)
Metadata-protocol: none
Order: as in the claim above. Measure first; if a shipped caller sends an
idon create, stop and report. One PR,Refs #29425, rule-only.objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQ6MkQtwGFr2gAD9qS
os-dev-report
{ "issue": 29425, "status": "needs_decision", "branch": "claude/project-thread-li91rx", "pr": null, "session": "session_01ADHkmQ6MkQtwGFr2gAD9qS", "premise_still_valid": true, "summary": "Letter C, step 1 (measure first) on origin/main a6441636, static reading, no code written. Three stop conditions hold. (1) A shipped caller sends an id on create in a non-system context: the shared import runner (packages/core/src/utils/import-runner.ts, flushPendingCreates) gives every buffered create row a minted id for its retry recheck and keeps a file-supplied id, and writes as the requester; it serves POST /data/:object/import, the async import jobs, the Console Import wizard and the SDK import. Clone overrides.id and the batch upsert create arm also create with a caller id. (2) ADR-0120's amendment of 2026-11-01 records the opposite rule ('Every create door accepts a caller-supplied id') and lists this exact refusal as rejected option a, with a bar for closing it; two dogfood files and http-protocol.mdx pin that contract, so C needs a superseding ADR record (Tier H). (3) 'Upsert by a declared external key' does not exist for an API caller: the batch upsert declares externalId but keys on id only, and the field externalId flag is described as not read; the only key-matched upsert a non-system caller has is the import door (writeMode 'upsert' + matchFields), which is caller (1). Detail: /mnt/project-files/server-seat5/29425-c-measurement.md (project files).", "tests": "none — read-only measurement, no diff", "gates": "n/a — no diff, no commit", "dogfood_battery": "n/a — no diff; owed once on the final head of the C PR", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #29425; no write tool", "api_writes": "1 — this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/29425/comments, through the fleet relay)", "open_questions": [ { "question": "Ruling C contradicts ADR-0120's 2026-11-01 amendment, and a shipped caller (the import runner) creates with an id as the requester. How do the callers move first, and does C supersede the amendment?", "options": [ "A: C stands. A superseding ADR-0120 amendment (Tier H, draft) lands with the code. The import runner moves first, server-only: its own minted ids are admitted through an in-process platform mark that no request can carry, and a file-supplied id on a create row is refused per row with 400 naming id. Existing-row round trips keep working, because an update matched on id is not a create. Clone overrides.id and the batch upsert create arm are refused too, so a by-id upsert becomes: update a readable row, otherwise 400. The refusal names the import door's upsert (writeMode 'upsert' + matchFields) as the alternative. Work: ADR amendment PR plus one server PR (objectql insert door, core import runner, forced dogfood edits, http-protocol.mdx text). BREAKING, no landed precedent.", "B: A, plus a real external-key upsert on the API. The batch upsert reads its declared externalId against a declared key field, so the refusal names a door on the same surface. Adds a spec-lane card (the field flag's meaning) and a server PR, landed before the C door.", "C0: Withdraw C and keep ADR-0120 as amended (caller-supplied ids accepted, a collision answers 409). Zero PRs on this letter." ], "recommendation": "B. Two years out, the identity model the ruling describes is engine-chosen ids plus a declared external-key upsert on every API door (the Salesforce model; Dataverse and ServiceNow, which the ADR cites, accept caller ids). B is the only option whose named alternative sits on the same surface the caller was refused on; under A, an API client keyed by its own ids has no API path left. Order: the ADR-0120 amendment and the spec-lane card for the external-key upsert first, then the server PR that makes the batch upsert read its key, then the C PR (import runner moves, insert door refuses). Confidence gaps: the run-time census of exercised create paths was not run (stopped at step 1), and the carrier of the importer mark (engine option vs row registry) is unmeasured." }, { "question": "Does 'system caller' include a context-less engine insert (no principal, no isSystem)?", "options": [ "A: No. The ruling's 'system identity only' is read literally: only isSystem: true passes, and a context-less insert carrying an id is refused like a member's, in line with ADR-0096 D5's fail-closed direction.", "B: Yes, for the transition. Context-less calls keep their id until ADR-0096 D5 flips, and the census lists them." ], "recommendation": "A. It is the ruling's own wording and ADR-0096's direction. The C PR runs the census first and moves any context-less id-sender to an explicit system context in the same PR." } ], "security_findings": "withheld", "out_of_scope_findings": [ "carrier: this card's decision (open question 1, option B) · the batch upsert's declared externalId row key is never read by the handler (spec:BatchRecordSchema.externalId -> consumer: none); NOT MEASURED at a public door · noted, not filed" ], "files_changed": [], "line_budget": "n/a — no diff", "deviations": [ "Stopped at step 1 under the delta's stop rule: no edit, no commit, no PR, no changeset.", "NOT MEASURED: a run-time census of non-system creates carrying an id over the dogfood battery. Reason: the static reading met the stop rule, and the ADR conflict puts the design with the maintainer. The census is the first step of the C PR once a letter is ruled.", "File surface reported, not claimed: if A or B proceeds, it reaches beyond the delta's expected surface. It adds packages/core (the import runner), docs/adr/0120 (governed, Tier H) and content/docs/protocol/kernel/http-protocol.mdx. B also needs a packages/spec card first.", "The dogfood closure build was started under the verify lock ahead of the implementation phase. It is a local build only and writes nothing to the repository." ] }Stamped 2026-11-02T15:22Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Needs decision · #29425 letter C · 2026-11-02T15:23Z
Ruling 6580257050 told the C PR to measure first, and to move any shipped caller that sends an
idon create before C lands. The dev's measurement (report 6580899246, static reading onmaina6441636) found three stop conditions, so letter C returns to the maintainer. Letter A is unaffected: spec #29442 proceeds.What stopped it (read in the report and spot-checked by the seat in the ADR text):
POST /data/:object/import, import jobs, the Console Import wizard and the SDK import. Clone (overrides.id) and the batch-upsert create arm do the same.id, and it lists this exact refusal as a rejected option, with a bar for reopening it: import round trips must keep working without an exemption that reopens the door. Two dogfood files andhttp-protocol.mdxpin that contract. C would need a superseding ADR amendment (Tier H).externalIdbut keys onidonly, and the field-levelexternalIdflag is described as not read. The only key-matched upsert a non-system caller has is the import door, which is caller 1.Options (maintainer):
Why the seat recommends C0: the maintainer amended ADR-0120 to accept caller ids one day before this ruling, against the same bar the ruling now meets. Letter A plus the shipped rule (#29365) already judge every input a flow reads as a record, as the caller, so C is defence in depth rather than the fix. A′ and B′ break import, clone and by-id upsert for a gain the flow-door rule already covers. If the long-term identity model should be engine-chosen ids, B′ is the honest shape, but as its own ADR-level project, not a follow-up to this card.
A second question is moot under C0. Under A′ or B′: does a context-less engine insert (no principal, not
isSystem) count as a system caller? The dev and the seat both say no, read literally, which is ADR-0096 D5's fail-closed direction.State:
pm:dispatched→needs-user-decision. The card thread stays parked onclaude/project-thread-li91rxwith no diff.