Skip to content

Yggdrasil → main: adopt the branch policy - #2

Closed
a777ance wants to merge 10 commits into
mainfrom
Yggdrasil
Closed

a777ance wants to merge 10 commits into
mainfrom
Yggdrasil

Conversation

@a777ance

@a777ance a777ance commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Draws this repo's Yggdrasil work into the Well.

A fresh session clones the default branch, and on main today this repo's CLAUDE.md mentions Yggdrasil zero times. Until this merges, a new session here reads a briefing with no branch rule at all — and a session reading silence invents one. That invention is what produced 337 stale claude/* branches, 226 carrying commits that exist nowhere else.

What's in it

The generated branch-policy block (Yggdrasil is the one standing working branch; main is the Well of Mimir and moves only by a PR the founder approves), rendered from localDNS canonical by tools/sync-briefings.py. Build output — do not hand-edit; edit the canonical file and re-run.

Briefing text only — no strategy content changed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DQFfkkSUPXDTgKizj6RfpF


Generated by Claude Code

claude added 10 commits August 8, 2026 11:02
Generated into this briefing from localDNS canonical
(04-user-services/ai-orchestration/branch-policy-block.md) by
tools/sync-briefings.py. Build output — do not hand-edit; edit the canonical file
and re-run.

This repo previously stated no branch rule at all. Absence is not neutral: a
session reading a briefing that says nothing about branching invents an answer,
and the invented answer produced 337 stale claude/* branches across the
portfolio. Yggdrasil is the one standing working branch; main is the Well of
Mimir and moves only through a PR the founder approves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQFfkkSUPXDTgKizj6RfpF
Rendered from localDNS/04-user-services/ai-orchestration/proxy-block.md by
tools/sync-briefings.py — build output, never hand-edited.

The agent git proxy and the egress proxy stand in front of this repo too, so the
briefing now says what actually refuses (enforced), what only asks (declared), and
what merely sits in the path (ambient).

Carrying the text alone would have shipped "never force-push Yggdrasil or main" with
nothing enforcing it — a declared boundary in the language of an enforced one, which is
the exact failure the doctrine names. So .claude/hooks/push-guard.sh is installed and
wired as PreToolUse(Bash): the agent git proxy blocks --delete but permits --force, and
both orphan commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bu1wcD4pBe8vK9S53wyQd7
Founder's standing instruction (2026-08-08): every session may list, inspect and
spawn sibling sessions without asking.

Two halves, because one alone does nothing:

- .claude/settings.json permissions.allow — the SITE. A briefing cannot
  pre-approve a tool call; the permission prompt never reads CLAUDE.md. This file
  is the grant. Both server spellings are listed (Claude_Code_Remote and
  claude-code-remote): an entry naming a tool absent from a session is inert,
  while a missing spelling costs a prompt, so listing both is the cheap direction.
- The generated session-visibility block in CLAUDE.md — the DOCTRINE, rendered
  from localDNS canonical by tools/sync-briefings.py. Build output; do not
  hand-edit.

Work runs in parallel across this portfolio — localDNS has had three sessions on
Yggdrasil at once, and two gate.sh conflicts inside a single turn. A session that
cannot see its siblings re-derives what they know and edits the file they are
editing. Taxing visibility with a prompt taxes the one behaviour that keeps
concurrent sessions out of each other's way.

interrupt/archive/unarchive are deliberately NOT granted: they reach into another
session's running state and can destroy work in flight. Seeing a sibling is not
the same as reaching into one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQFfkkSUPXDTgKizj6RfpF
Founder extended the standing grant (2026-08-08) beyond session visibility to
scheduling and repo attach. Added to .claude/settings.json permissions.allow in
all ten repos:

  list_triggers, create_trigger, update_trigger, send_later
  add_repo, list_repos, register_repo_root
  set_session_title, set_session_tags

Still withheld, and now enforced as such: delete_trigger, fire_trigger,
interrupt_session, archive_session, unarchive_session. Each destroys something or
fires an effect NOW. Creating a routine is additive and visible; deleting the
founders routine, or firing one early, is neither.

Triggers are the one grant that acts when nobody is watching — everything else
happens in-turn, in view. So the block states that a trigger inherits the one-way
door rather than escaping it: it may prepare, report and check, but it may not be
the thing that performs an irreversible outward-facing action. Publish, deploy,
send, delete, merge to main wait at the * gate whatever the cron says. And the
grant may not be used to schedule later what was refused now — that launders the
decision as surely as routing it through a sibling.

check_session_grant now runs BOTH directions: it fails if a promised tool is
missing, and it fails if a WITHHELD tool quietly appears in an allow-list. A grant
that shows up unannounced is the same briefing/settings disagreement as one that
vanishes, and only one of those was previously catchable. Exercised both ways
before landing (drop add_repo -> exit 1 naming it; add fire_trigger -> exit 1
naming it; restored -> exit 0).

The section heading widened from "Session visibility" to "Session tooling" across
all ten briefings; the marker keeps its original name so existing renders stay
addressable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQFfkkSUPXDTgKizj6RfpF
Every running session is a Norn (founder's standing instruction, 2026-08-08). The
promotion protocol is injected by .claude/hooks/refeed.sh at SessionStart so it enters
every session's given-set at token zero: get onto Yggdrasil (a fresh clone lands on
main, where the rule saying to use Yggdrasil is invisible), look before you write, claim
a lane before the work, and front-load the repos and approvals you will need instead of
stalling halfway for one you could have requested up front.

The limit is stated hard: promotion grants NO new permissions. Being a Norn is a duty,
not a capability. A blocked action never gets routed through a peer session — that
launders the founder's permission decision — it goes back to the founder.

The hook here is a shared body plus a two-line per-repo config (MANIFEST, QUEUE), so the
block was injected into the body rather than overwriting the file, defined once and
referenced from both the queue and no-queue branches. Exercised: valid JSON, block
present, both branches.

Also carries the session-visibility block (rendered) and the grant it describes:
list_sessions, get_session, create_session and list_environments in permissions.allow.
The block's own check refuses to pass while a repo claims the grant without holding it,
so the doctrine and the settings file cannot drift apart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bu1wcD4pBe8vK9S53wyQd7
Both sessions rendered generated blocks into CLAUDE.md at once. Those blocks are build
output, so the conflict was resolved by taking the remote file and re-running the
generator rather than merging text by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bu1wcD4pBe8vK9S53wyQd7
The session-tooling block says the grant lives in each repo's settings.json, and its
own check refuses while a repo claims a grant it does not hold. localDNS had the full
list; the siblings had none of it, then a partial one. Mirrored from localDNS as the
reference so doctrine and settings cannot disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bu1wcD4pBe8vK9S53wyQd7
"Every running session is a Norn" was sited only on startup|clear. A re-activated
session sends source=resume, and the hook exited early: no promotion, no sync, nothing.

Resume is the more dangerous half. A session that went idle carries an IN-CONTEXT
briefing from before it stopped — one that may still say "push to main, no branches" —
and a stale briefing cannot tell that it is stale. Several idle sessions are parked on
retired claude/* branches right now; re-activating one would have pushed to a branch the
doom drawer exists to retire, while believing it was following current doctrine.

The startup|clear gate is still right: re-pulling and re-injecting mid-work is exactly
what it prevents. So resume gets a cheap staleness test instead of the full ritual —
read-only (fetch, never pull), and it speaks ONLY when the session is not on Yggdrasil
or is behind it. Resumed on Yggdrasil and current, it stays silent as before.

When it does speak it leads with the thing the session cannot work out for itself: your
briefing may be stale, re-read CLAUDE.md from disk before trusting the branching advice
you are already holding. Then the branch move, the weave check, the lane claim, and the
permission limit.

Exercised in all ten: startup emits, resume-and-current stays silent, and a resume
simulated on a retired claude/* branch (153 commits behind) emits with the stale-briefing
warning, the retired-branch warning, and the permission limit intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bu1wcD4pBe8vK9S53wyQd7
…went stale

Two repairs, both from the same cause: doctrine moved overnight and the
instruments did not notice.

1. THE LADDER REACHED ONE REPO. The cream-rises promotion ladder (2026-08-09)
   landed in localDNS canonical, but all nine sibling briefings still taught the
   superseded "one standing working branch" rule. sync-briefings caught it across
   sessions — exactly the parallel-session drift it exists for — and the nine are
   now rendered from canonical.

2. HLIDSKJALF WAS CONFIDENTLY WRONG, on its first full day. Its top-ranked
   decision told the founder to approve ten Yggdrasil -> main PRs. Those are
   whole-branch merges into a rail, and tools/check-promotion.py now REFUSES that
   shape — verified directly: a rail head exits 1. The seat was recommending the
   one action the lock rejects.

   That is precisely the failure the seat was built to catch, turned on itself: an
   instrument reading a world that changed under it, with no way to know. So
   decide() now detects the lock and emits the ladder instead — cut promote/* from
   main, cherry-pick the chosen commits, PR that branch — and names the open
   whole-branch PRs as dead shape to CLOSE, never approve.

Re-sitting the seat immediately proved the point twice more: it re-measured
claude/* refs at 2, down from 339 (the founder ran the retirement — 337 refs
gone, every drawer intact), and it raised its own PR snapshot as 71h stale. An
instrument that reports its own staleness is the only kind worth trusting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQFfkkSUPXDTgKizj6RfpF

Copy link
Copy Markdown
Owner Author

Closing as superseded shape, not rejected work — the content is wanted; this route to main no longer exists.

The branch policy moved on 2026-08-09 to the cream rises — promote by cherry-pick, one rung at a time. main is now reachable only by a promote/* branch cut from main carrying specifically chosen commits. A whole-branch merge from a rail is refused mechanically by tools/check-promotion.py (the promotion-guard check), so this PR cannot land even if approved — its head is Yggdrasil, a rail.

Nothing here is lost. Every commit stays on Yggdrasil, the rung directly below the Well; promoting it is now an act of selection rather than a transfer.

The replacement route:

git checkout -b promote/<topic> origin/main
git cherry-pick <the chosen commits>
git push -u origin promote/<topic>   # then open the PR from that branch

localDNS #31 is the worked example of the new shape.


Generated by Claude Code

@a777ance a777ance closed this Aug 11, 2026
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.

2 participants