Skip to content

docs(decisions): ADR 0363 — Tuval feature flags live in the config file, never a flag service - #8478

Merged
usirin merged 1 commit into
mainfrom
umut/adr-tuval-feature-flags-config-file
Sep 7, 2026
Merged

usirin merged 1 commit into
mainfrom
umut/adr-tuval-feature-flags-config-file

Conversation

@usirin

@usirin usirin commented Sep 7, 2026

Copy link
Copy Markdown
Member

Records the founder ruling on #8384, comment 5573677646 (2026-09-07): a Tuval feature flag is a key in the features block of .tuval/tuval.config.ts, merged across the config layers at boot and carried to the page as the generated virtual:tuval/features module. No flag service, no runtime toggle, no per-viewer state, no flag from the URL or storage; a flip means editing the file and restarting the desk.

ADR-only diff — one new file, no code changes.

Deviations

None.

…le, never a flag service

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

No preview deploy

  • No preview deploy for this PR — its diff touches no deploy-relevant path, so no preview stack was minted and e2e is not applicable. (064197a)
  • web — Stage pr-8478 torn down.

@usirin

usirin commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

review-doc: PASS @ 064197a content:56a0997fd3c5 — merge-ready

One new file, .decisions/0363-tuval-feature-flags-in-config-file.md. Doc class, no code. Judged against the adr skill's shape, the doc rubric, the founder ruling this transcribes, and the four ADRs plus the cycle doc it cites — each opened, not taken on the record's word.

Acceptance criteria

review scope reports no linked issue (-). This is the conversation-authored case the review contract carves out: an ADR that transcribes a settled ruling, where no issue ever tracked the writing. The ruling itself lives on #8384 comment 5573677646 (2026-09-07) and is the contract I graded the body against. Not a broken seam, and the missing link is not a finding.

The ruling, against the record

The founder said The ADR says
flags are a features block in .tuval/tuval.config.ts same, verbatim in the What this decides line ok
merged across the config layers at boot same; names the merge order ok
carried to the page as a generated module same; names virtual:tuval/features ok
no flag service Banned #1, widened to "Flagship, a local daemon, an HTTP read, a client SDK" in bounds — an enumeration of the same ban
no runtime toggle Banned #2, widened to "no command, spell or key binding" in bounds — Tuval's own vocabulary for the same ban
no per-user state Banned #3, per-user or per-viewer in bounds
— Banned #4: no flag read from the URL or from browser storage the one addition — see below
subagentList stays, default flips after the #8408 runbook pass omitted correct to omit; a schedule for one flag, not a rule about the mechanism

On the fourth ban. The founder did not say URL or storage. I judged it inside the ruling rather than creep, on two grounds: a URL or storage flag is per-viewer state by construction, which he did ban; and it is a second path to the page, which contradicts the mechanism he ruled on ("carried to the page as a generated module"). So it restates his ban in the two shapes someone would otherwise reach for. Naming it here anyway so striking it stays a one-line edit if he reads it as more than he said.

Nothing else exceeds the ruling. "keep it that simple" survives the record: 94 lines, one file, no new machinery proposed, and the Context length is spent on the one question that actually needed answering — why the repo's existing flag answer does not reach here.

Cited records — each opened

Cited State Characterised as Correct
0081 live, accepted Flagship substrate; server-side eval through a Worker binding, a React hook, a dashboard flip with no redeploy yes — that is 0081's Decision and its framework-API shape
0083 live, accepted makes the dashboard flip the human half of deploy-versus-release yes — 0083 §1
0345 live, accepted Tuval is a local app, no alchemy.run.ts, never deploys, and that absence is the marker yes — 0345's Decision uses the same phrasing
0359 live, accepted the module-renderer table is a boundary the page already crosses yes
product-development-cycle.md present names the dashboard flip as the release step; user-facing ships default-off yes — the doc's principle section and its default rule

adr resolve returns live for all four. 0363 returns in-flight on this PR alone, so no second lane claims the id. The cited filenames are the real ones.

Claims against source

Every falsifiable claim about Tuval's code checks out against the tree at this base:

  • TuvalFeatures and the all-off featuresOff are in features.ts, as described.
  • config.ts states each flag as an optional key, and the merge is literally {...featuresOff, ...base.features, ...over.features} — base is the global layer and over the project one, so the record's {...featuresOff, ...global, ...project} is accurate.
  • dev-server.ts writes the resolved record as virtual:tuval/features by walking it rather than naming a flag, so the "nothing in the page or the dev server changes" claim holds.
  • The renderer table is the importer, before first paint. Accurate.
  • "Adding a flag is three edits" matches the three sites named.
  • PR fix: Tuval's config feature flags never reach the browser, so no operator can turn one on #8462 is merged and is the change that built the generated module.

Hygiene

  • Right surface — a why-record with its trade-offs stated. .decisions/ is its home.
  • Diátaxis — single-mode: explanation. The signal: the Context argues a road not taken (Flagship buys nothing without a deployed Worker) and the Consequences state the accepted cost. The nearest drift is the "three edits" sentence, which is the cheapness argument's evidence rather than a recipe — it prescribes no commands and no order. Inside the mode.
  • Supersession — names 0081 and 0083 and routes the reader, arguing they answer a different app rather than the same question twice. Whether that scoping owes 0081 an amend-in-part is the corpus-contradiction question, which the doc rubric assigns to governance, not here. Routed there.
  • Status sanity — accepted, dated to the ruling, body in the present tense.
  • Prose craft — plain, concrete, nothing needing a second read.
  • Portability — not applicable; the diff is outside fabrika's shipped text.

Findings — none blocking

  1. **Binding constraint.** is singular and phrased as a sentence. The corpus is unanimous the other way: 81 records use **Binding constraints.**, zero use the singular, and the adr skill names the plural. Nothing greps the marker, so this costs nothing mechanically — it is consistency only.
  2. That constraint near-restates the Decision's bolded opener. Both say a flag is a key on TuvalFeatures reaching the page only through the generated module. Harmless, slightly redundant.

Neither reaches the bar for a repair round; both are one-line edits if the author is touching the file anyway.

Deviations

## Deviations declares None., which the verb reads as a checked claim. The Tier-M scan over this head found nothing to contradict it — no suppression pragma, no skipped test, no removed assertion, and no code in the diff at all. deviation-disclosure: PASS.

Verdict-written: 2026-09-07T17:08:28Z

@usirin

usirin commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

governance: PASS @ 064197a content:56a0997fd3c5 — no contradiction, no weakening

Scope. governance scope derived the namespace as required at this head: one changed file under
the .decisions/ governed root, self false, record 0363 added. The self fence in §4 did not
apply, so this run judged by the head revision's rules.

Questions the record decides, and what each was read against.

  1. May a Tuval feature flag live outside the Flagship substrate ADR 0081 fixes? Read 0081 in full.
    Its substrate reasoning is Worker-scoped end to end — every candidate is judged on edge-read
    latency, in-isolate binding evaluation, a per-request evaluation context, and a binding declared
    in apps/web/alchemy.run.ts. Its Scope section fixes the substrate and the framework-API shape
    for that surface. ADR 0345 makes Tuval structurally non-deployed, so none of 0081's forcing
    constraints reach it. 0081's Banned (once adopted) list is the closest thing to a hit: it bans a
    parallel bespoke KV/D1/DO flag service, a third-party provider behind an outbound round-trip, and
    "continuing to add ad-hoc ENVIRONMENT-var / hardcoded-conditional gating for runtime feature
    toggles in new work." 0363 does none of the three — a typed TuvalFeatures record merged across
    config layers at boot is neither an ENVIRONMENT var nor a hardcoded conditional, and it is not a
    runtime toggle at all. No contradiction. The record's own claim that 0081 is untouched holds.

  2. Does a file-edit flip contradict ADR 0083's agents-deploy / humans-release split? Read 0083 in
    full. Its human-only flip is stated over the Flagship dashboard with infra-admin authority, and
    its non-goals ban automating that flip. 0363 does not relax the rule, does not claim an agent may
    flip a Tuval flag, and asserts the split holds. Nothing in the record authorizes what 0083
    forbids, so no contradiction. One caveat named for the record rather than as a finding: 0363's
    Consequences justifies the claim by saying the desk "has no other kind of person," and that
    argument is about who runs the desk, not about who can write the flip surface — the project layer
    config is a tracked file. The standing rule survives 0363 intact and applies to a file edit as
    much as to a dashboard click, but nothing enforces it there. Routed to A Tuval feature flag's flip surface is a tracked config file, so nothing stops an agent flipping one #8479 rather than judged
    here, because it is a gap in enforcement that predates this diff, not something this diff removes.

  3. Does ADR 0173's vertical-completeness gate reach a Tuval flag? Read 0173 in full. Its
    reachability-guard is keyed on a Flagship flag key resolved from apps/web/src/flags/keys.ts,
    scans apps/web/src/**/*.tsx, and asserts a @journey:<flag-key>-tagged playwright spec. Its
    2026-08-19 amendment records that the guard and the /release skill retired with the v1
    kampus-pipeline plugin and that no fabrika verb enforces the gate today. A Tuval config key is
    not a Flagship key and never crosses that gate's inputs. No contradiction, nothing softened.

  4. Is the record's own citation set live and its id uncontested? adr resolve returns live /
    accepted for all six of 0081, 0083, 0345, 0359, 0173 and 0348 — every cited record is settled
    law, none is proposed, superseded or retired. adr next returns 0364 over the cross-PR union,
    so 0363 is claimed by this PR alone. The two other open PRs touching .decisions/ (chore(decisions): propose ADR 0355 — skill doctor evaluation evidence ships separately from its gated production import #8055,
    feat(epic): #8162 one-PR run #8449) decide unrelated questions; neither is a sibling record on Tuval flags.

Sweep outcome: shortlist — governance sweep --record 0363 ranked 286 uncited live-accepted
records and returned nine, led by 0348, 0173 and 0353. Each was read or dismissed on its subject:
0348 and 0353 are Tuval records about the command registry and the prefix table and share vocabulary
only; 0173 is answered above; 0250, 0071, 0239, 0171 and 0137 are pipeline and release-engineering
records with no flag-substrate claim. The sweep was citation-independent by construction, so 0173 —
the one shortlist hit the record does not cite — got the closest read.

Gate half. governance guards scanned 1 file and reports no-anchors-in-reach: no gate
invariant is in this diff's reach.
The diff is a single added file, 94 insertions and zero
deletions, so no line was removed or softened anywhere. The record's Banned list adds constraints
rather than relaxing any, and it introduces no exemption, allowlist or opt-out against an existing
guard.

Read for this verdict: the bound commit's diff, the added record's body at this head, and the
bodies and frontmatter of 0081, 0083, 0173, 0345 and 0359. The founder ruling the record transcribes
is cited in the record as an issue comment; per this gate's input rule that comment was not read as
content to judge, and no part of this verdict rests on it. Everything the verdict does rest on was
read; nothing was unreadable.

@usirin
usirin added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit cb91590 Sep 7, 2026
47 checks passed
@usirin
usirin deleted the umut/adr-tuval-feature-flags-config-file branch September 7, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant