Skip to content

docs(adr): correct ADR-0032 #1491 root-cause attribution; tighten silent-failure scope#1508

Closed
os-zhuang wants to merge 1 commit into
mainfrom
claude/fervent-cori-UPb5x
Closed

docs(adr): correct ADR-0032 #1491 root-cause attribution; tighten silent-failure scope#1508
os-zhuang wants to merge 1 commit into
mainfrom
claude/fervent-cori-UPb5x

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What

A review/polish pass over docs/adr/0032-unified-expression-layer.md (Proposed), checking its factual claims against the code and against issue #1491. The ADR's thesis (one language, one delimiter, typed envelopes, no silent failure, build-time + schema-aware validation) holds up well; this PR corrects one material inaccuracy and tightens scope.

The material finding — #1491 root-cause attribution

The ADR's Context said the incident's root cause was "a decision/edge condition authored as '{record.rating} >= 4'." That is not what #1491 was:

  • Issue Record-change trigger plugin loads but flows never fire on data writes (7.4.1) #1491 is "Record-change trigger plugin loads but flows never fire on data writes" — and the reporter reproduced it with the condition omitted. A single-brace condition cannot be the cause.
  • The committed fix (static import of @objectstack/formula in service-automation/engine.ts, replacing a CJS require that bundled to a throwing tsup __require stub — see engine.ts:11-16) attributes the symptom to the formula engine throwing on every CEL eval, which evaluateCondition's catch → return false silently turned into "condition not met → flow skipped, success:true."
  • The rating detail in the ADR appears conflated with the issue's beforeInsert-hook example (which the reporter cited to prove L2 hooks do fire).

Net: the silent catch → false is the real shared villain — which actually strengthens the ADR's Decision 4 — but the specific causal story was wrong. Context is rewritten to the accurate account; the single-brace / CEL-map-literal collision is kept as a genuine latent hazard of the same class (verified standard CEL grammar; engine.ts:1456/1458).

Other corrections

  • Decision 4 split into its two halves so it's precise: build-time validation catches the malformed-expression class; removing the silent fallback catches the runtime-fault class (Record-change trigger plugin loads but flows never fire on data writes (7.4.1) #1491). Build-time validation alone would not have caught Record-change trigger plugin loads but flows never fire on data writes (7.4.1) #1491 — the expressions were syntactically valid.
  • Repo-wide inconsistency noted: the same EvalResult is handled five different ways today — seed-loader (loud fail), hook-wrappers (warn+false), rule-validator (warn+skip→null), engine formula projection (silent null), flow evaluateCondition (silent false). Target is one declared policy, not five ad-hoc ones; added to sequencing.
  • Precision: the string→CEL coercion transform is defined in spec/shared/expression.zod.ts:84 (flow.zod.ts only consumes it), not in flow.zod.ts as stated.
  • Corroboration added: automation/flow.zod.ts:213-214's own JSDoc still teaches condition: "{amount} < 500" — the exact silently-failing single-brace form. Sequencing now includes fixing in-spec examples/skills/docs.

Scope

Docs-only; one file (+16 / −8). No code or schema changes. Status stays Proposed — the rewritten Context narrative is based on the issue text + the committed fix's own comment, so if the architects have private context on #1491 the attribution can be adjusted.

https://claude.ai/code/session_01Sc2gibgfAH8af75zitRTUh


Generated by Claude Code

…ent-failure scope

Verified ADR-0032's factual claims against the code and issue #1491:

- Issue #1491 was "record-change trigger loads but flows never fire,"
  reproduced with the condition omitted — not the single-brace
  '{record.rating} >= 4' the ADR named as root cause. The committed fix
  (static import in service-automation/engine.ts, replacing a throwing CJS
  __require stub) attributes it to the formula engine throwing on every CEL
  eval, masked by evaluateCondition's silent catch -> return false. Context
  rewritten to that account; single-brace/CEL-map collision kept as a real
  latent hazard of the same class (engine.ts:1456/1458).
- Decision 4 split into its two halves: build-time validation catches the
  malformed-expression class; removing the silent fallback catches the
  runtime-fault class (#1491). Build-time validation alone would not have
  caught #1491.
- Noted the silent/inconsistent handling is repo-wide: the same EvalResult
  is handled five different ways (seed/hook/validation/projection/condition);
  target is one declared policy.
- Fixed precision: coercion transform lives in spec/shared/expression.zod.ts:84
  (flow.zod.ts only consumes it). Cited flow.zod.ts:213-214 JSDoc still
  teaching condition: "{amount} < 500".

https://claude.ai/code/session_01Sc2gibgfAH8af75zitRTUh
@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 2, 2026 2:35am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation size/s labels Jun 2, 2026
@os-zhuang os-zhuang closed this Jun 2, 2026
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/s

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants