Repository navigation
Specs policies
This specification defines the policy object — a named rule with a value, set at one level by a named setter — its four signed level sources, the closed catalogue of names the release owns with defaults and tightness directions, the commands that read and write it, the resolver that walks the chain, and the gate-lifting runtime. The refusal the object backs is the Kernel's map's, not this specification's. The catalogue's prose (what each name means, how to add one) stays in the catalogue, the living page; this specification is the code's shape.
-
policy: one named rule with a value, set at one level, by a named setter, on a date, with an optional expiry and a kind (src/policy/types.ts). -
level: one of entity, project, team, seat — the four inheritance levels, outermost first. -
kind: enforced or advised. Enforced names are a closed, release-owned vocabulary; advised names may be the catalogue's or a project's own. -
chain: the four sources walked outermost to innermost; the effective value is the innermost unexpired declaration that is within every outer one. -
within: the tighten-only test — a lower level's value must be tighter than or equal to the higher's, judged per name (isWithin). -
gate-lifting: a policy whose absent or expired value reads asoff— the default never silently stands in when a level spoke (gateLifting). -
domain tag: the constant prefix a signature covers,POLICY_DOMAIN='team-policy-v1\n', so a signed policy verifies as nothing else. -
generation: the owner key store's signing sequence, one per source write. -
gate lift: thegate-liftpolicy (in code:dev-mode, until the rename ships) lifting the caller gate of nine everyday operations; the compatibility environment variable is honoured only while no value governs.
One declaration at one level, as Kernel amendment 02 §2.1 requires it, carried in code as Policy (src/policy/types.ts):
| Field | Type | Required | Default | Since | Meaning |
|---|---|---|---|---|---|
name |
canonical name | yes | — | 0.5.0 | Kebab-case; snake-case aliases are normalized to it at load |
value |
'on'/'off', boolean, number, string, or an ordered list | yes | — | 0.5.0 | What the name is set to at this level |
level |
entity, project, team, seat | yes | — | 0.5.0 | Where it is set |
setter |
string or null | yes | — | 0.5.0 | Who set it; the owner in 0.5.0, or null for an unsigned legacy delivery_order: declaration |
date |
ISO 8601 or null | yes | — | 0.5.0 | When set; null only for unsigned legacy declarations |
expiry |
ISO 8601 or null | no | null | 0.5.0 | When the declaration expires; an expired declaration is unset at its level |
kind |
enforced or advised | yes | — | 0.5.0 | Fixed by the defining text, not chosen at write |
target |
id | no | — | 0.5.0 | What the signature covers: entity name, project id, team id, or agent id |
generation |
integer | no | — | 0.5.0 | The signing sequence of the write |
signature |
Ed25519 | no | — | 0.5.0 | Over POLICY_DOMAIN = 'team-policy-v1\n'
|
On disk in a signed source the entry carries value, setter, date, expiry, kind, target, generation, signature under the policy's name (SignedPolicyDiskEntry, src/policy/sources.ts); name is the key it sits under, level is which of the four files it sits in. A record that does not verify under the domain tag is not in force.
One signed source per level, each read as a policies: map (src/policy/sources.ts):
| Level | File | Where |
|---|---|---|
| entity | entities.yaml |
<home>/.config/team/entities.yaml, under <entity>.policies
|
| project | projects.yaml |
<home>/.config/team/projects.yaml, under projects.<projectId>.policies
|
| team | team.yaml |
the team root's .agents/team.yaml, owner policies: section |
| seat | agent file |
<home>/.config/team/agents/<agentId>.yaml, under policies
|
Writer: the owner only, at team policy set / team policy unset (§4). A source that cannot be read, verified or ordered fails closed: nothing is lifted on it, the default does not silently stand in, and the run says why in its log.
Kinds, allowed levels, release defaults and tightness directions, from POLICY_CATALOGUE at the tag (src/policy/types.ts, defaults sourced from src/settings/inherit.ts):
| Name | Kind | Levels | Default | Tighter is | Note |
|---|---|---|---|---|---|
gate-lift |
enforced | entity, project, team | off |
off; a nearer expiry is tighter than a later one or none | gate-lifting; in code dev-mode, until the rename ships; excludes the seat level |
seats-cross-teams |
enforced | entity, project, team, seat | from the settings fallback | off | — |
state-trace-window-seconds |
enforced | entity, project, team, seat | from the settings fallback | shorter or equal | — |
state-blind-after-seconds |
enforced | entity, project, team, seat | from the settings fallback | shorter or equal | — |
state-hook-working-seconds |
enforced | entity, project, team, seat | from the settings fallback | shorter or equal | — |
liveness-idle-lead-seconds |
enforced | entity, project, team, seat | from the settings fallback | shorter or equal | — |
liveness-idle-owner-seconds |
enforced | entity, project, team, seat | from the settings fallback | shorter or equal | — |
delivery-order |
enforced | entity, project, team, seat | the legs order fallback | dropping or reordering channels the higher level holds; adding one it excludes is not within |
socket-accepted is not available in this release |
development-mode |
advised | entity, project, team | local-fast |
unordered: a lower level chooses (lowerLevelChooses) |
values local-fast or pr-per-change
|
shared-checkout-hygiene |
advised | project, team | on |
on | — |
seat-numbering |
advised | entity, project, team | from the settings fallback | unordered: a lower level chooses | width 1–4 digits or auto, plus separator, as one canonical string |
pane-conduct |
advised | entity, project, team | on |
on | — |
board |
advised | project | '' |
equal | a tracker pointer |
review-terms |
advised | project | '' |
equal | — |
Eight enforced names, six advised. Snake-case spellings are normalized to canonical kebab-case at load (POLICY_NAME_ALIASES); seconds values parse as integers or 120s-style strings; delivery-order parses as a comma-separated or listed permutation of the delivery legs with no duplicate and refuses socket-accepted in this release. A name in the catalogue may not be set at a level it does not allow, and the refusal names the allowed levels.
team policy lists the effective policies with the value in force and where it comes from; --seat reads a seat's chain, --file names the team file, --json machine-reads the same. The write commands plan before they write: --dry-run reaches exactly what a real run would refuse and writes nothing.
From contract/cli.json at the tag:
team policy [--seat <name>] [--file <path>] [--json]
team policy set <level> <name> <value> [--until <iso>] [--kind <enforced|advised>] [--entity <id>] [--project <id>] [--seat <name>] [--file <path>] [--dry-run] [--json]
team policy unset <level> <name> [--entity <id>] [--project <id>] [--seat <name>] [--file <path>] [--dry-run] [--json]
<level> is one of entity, project, team, seat; seat is given as --seat <name> or as the level word seat:<name>. Exit codes from contract/exit-codes.json:
| id | code | meaning |
|---|---|---|
policy.done |
0 | the effective policies were listed, updated, or planned |
policy.refused |
1 | a non-owner attempted set or unset, a lower level loosened a higher level, or a source failed verification |
policy.invalid |
2 | the team file, policy name, or value is invalid |
policy.invocation |
2 | the invocation can't be read |
-
Only the owner sets and unsets. A non-owner's
policy setorpolicy unsetMUST be refused withpolicy.refused; in 0.5.0 there is no grant that carries the setter. Source:src/commands/policy.ts,contract/exit-codes.json. Proof:test/policy.test.ts. -
One signed source per level. An entity policy lives under its entity, a project under its project id, a team in its
.agents/team.yamlowner section, a seat in its agent file; every write is signed underPOLICY_DOMAINwith the target and generation covered. Source:src/policy/sources.ts. Proof:test/policy.test.ts. -
A lower level may only tighten. A declaration that is not within every outer level's MUST be refused at write with
policy.refused; the tightness direction is per name (§3.3). The exception is the names whose defining text says a lower level chooses (development-mode,seat-numbering). Source:POLICY_CATALOGUE'sisWithin,lowerLevelChooses. Proof:test/policy.test.ts. -
The innermost unexpired declaration wins. An expired declaration is unset at its level; the chain reads the next one out. Source:
src/policy/resolve.ts. Proof:test/policy.test.ts. -
Gate-lifting policies fail to off. A gate-lifting name with no effective value reads as
off— the default never silently stands in for a level that spoke and expired. Source:gateLiftingin the catalogue,src/dev_mode.ts. Proof:test/dev-mode-gates-matrix.test.ts. -
An unreadable source fails closed. A source that cannot be read, verified or ordered lifts nothing and sets nothing; the run reports it rather than falling back. Source:
src/policy/sources.ts,src/dev_mode.ts. Proof:test/dev-mode-gates-matrix.test.ts. -
Gate-lifting lifts nine operations' caller gate, and nothing else. With
gate-lifton, the caller gate ofup,down,removeof an ordinary seat,register,add,restart,init,reload,rename— the owner's seven (OD-20261017-01; exactly those, OD-20261017-08) plus two more, read as the same words — folds into a warning on the error stream with a log line per use, in every running team; it never reachesgrant,delegate,revoke,approve,create,agree,leads, a lead seat's removal or add,remove --abandon,broker,tempo, or a delegate's file-editingadd. Source:DEV_MODE_OPERATIONS,src/dev_mode.ts. Proof:test/dev-mode-gates-matrix.test.ts. -
The environment variable is a migration trigger only.
TEAMCLI_DEV_MODE=1is honoured only while nogate-liftvalue is set anywhere in the chain; once a value governs — an expired declaration included — the variable is ignored and its ignoring logged once, and the first command after an expiry names it. Source:src/dev_mode.ts. Proof:test/dev-mode-gates-matrix.test.ts. -
Names normalize at load. Snake-case spellings of catalogue names are read as their canonical kebab-case names; a value that does not parse for its name is refused with
policy.invalid. Source:normalizePolicyName, the per-nameparseValue. Proof:test/policy.test.ts. -
The entity's model rule is an entity-level policy (proposed). The rule for which models may run on which seats MUST be held as one policy at entity level, and the tool MUST carry it out in every team of the entity: it lists the seats that break it, proposes each replacement, swaps one by one with the handover and reads the status bar back, and
team checkandteam watchflag every breach. Decided by: OD-20261019-01 (Kernel amendment 02's fold). Proof: none at the tag — the name is not insrc/policy/types.ts. -
A queued model swap waits, and the wait is set (proposed). A swap queued under the model rule waits for the seat's task boundary; after the set wait — the
swap-wait-secondspolicy, 30 minutes proposed — it MUST run at the next pause between two turns, and the run MUST be logged; a broken seat swaps at once. Decided by: OD-20261019-03, with -02's task boundary. Proof: none at the tag. -
The minimum review labs are a setting (proposed). The naming-method text and every casting MUST be reviewed by at least the number of labs the
review-labs-minpolicy sets: entity, project or team level, default 2, labs other than the author's; the setting counts labs, not reviewers — Quality'sreview-countis another setting. Decided by: OD-20261019-20. Proof: none at the tag.
Rows for the fourteen names in force at the tag (in-tool table), one line per entry in the row shape (entry | meaning | since | source at the tag | note):
-
gate-lift| lifts the caller gate of the nine everyday operations | 0.5.0 |src/policy/types.ts| gate-lifting; in codedev-mode, until the rename ships; off by default; excludes the seat level -
seats-cross-teams| whether a seat may sit on more than one team | 0.5.0 |src/policy/types.ts| off is tighter -
state-trace-window-seconds| how fresh a trace must be for a seat to read as working | 0.5.0 |src/policy/types.ts| shorter is tighter -
state-blind-after-seconds| how long unknown reads as blind | 0.5.0 |src/policy/types.ts| shorter is tighter -
state-hook-working-seconds| how long a hook's working signal holds | 0.5.0 |src/policy/types.ts| shorter is tighter -
liveness-idle-lead-seconds| idle this long tells the lead | 0.5.0 |src/policy/types.ts| shorter is tighter -
liveness-idle-owner-seconds| idle this long tells the owner | 0.5.0 |src/policy/types.ts| shorter is tighter -
delivery-order| the legs a send tries, in order | 0.5.0 |src/policy/types.ts|socket-acceptedunavailable in this release -
development-mode| how building work moves to the trunk | 0.5.0 |src/policy/types.ts| advised; a lower level chooses;local-fastdefault -
shared-checkout-hygiene| the shared checkout's hygiene rules | 0.5.0 |src/policy/types.ts| advised; on is tighter -
seat-numbering| the width and separator of a seat's number | 0.5.0 |src/policy/types.ts| advised; a lower level chooses -
pane-conduct| the pane conduct rules | 0.5.0 |src/policy/types.ts| advised; on is tighter -
board| the team's tracker pointer | 0.5.0 |src/policy/types.ts| advised; project level -
review-terms| the terms a review runs under | 0.5.0 |src/policy/types.ts| advised; project level
Planned rows (decided in texts, not in code at the tag):
-
forward-only-install| the install step refuses an older version without a declared rollback | planned | 010 Workflow §2d | advised until the Kernel names the gate -
round-close-sequence|round closerefuses to skip or reorder a step | planned | 010 Workflow §2d | advised until the round is a command -
model-rule| the entity's rule for which models may run on which seats, carried out in every team of the entity | planned | Kernel amendment 02's fold (OD-20261019-01) | entity level only; a breach flagged byteam checkandteam watch -
swap-wait-seconds| how long a queued model swap waits at the task boundary before running at the next pause between two turns, logged | planned | Kernel amendment 02's fold (OD-20261019-03) | 30 minutes proposed; a broken seat swaps at once -
review-labs-min| the least number of labs, other than the author's, that review the naming-method text and every casting | planned | Kernel amendment 02's fold (OD-20261019-20) | default 2; labs, not reviewers (review-countis another setting)
- No ledger record yet: a policy change is signed in its source but not written to the ledger as an event; the object's §2.11 record is decided and not built.
- No owner notice yet: a set or unset does not tell the owner beyond the log line.
- No non-owner setters yet: the amendment's grant-carried setter is decided and not built; the owner is the only setter in 0.5.0.
- Advised showing: whether an advised policy's value is shown where it is followed (briefs, checks) is not built.
- One value per name per level: the code holds one value; two declarations of one name at one level are a YAML error, so the amendment's "the stricter stands" rule has no case to decide at the tag.
- A project's own advised names: the kind allows them; the catalogue holds only the release's fourteen, and what a name outside the catalogue gates is its defining text's business, nothing in the code's.
-
The seat level's reach: a seat-level policy binds the named seat only; whether a seat may read every level above it in one listing (
--seattoday) or only its effective values is a display question still open.
-
Draft (the writer, 2026-10-18): rebuilt for Kernel amendment 02 at
v0.5.0 (52db9b75)from the code — the object (§3.1, the amendment §2.1 field list), the four sources, the fourteen-name catalogue with defaults and tightness directions, the commands and exit codes, nine rules and the policy-names rows; replaces the earlier anonymous draft of 21:10, which was verified against the same tag before its removal. -
Review fold carried in (the writer, 2026-10-19): rule 7 takes what the amendment's §2.7 handed over at the fold — the ordinary-seat qualifier on
remove, the decision anchors on the owner's seven, the seven-plus-two reading, the warning on the error stream with a log line per use, and a lead seat's removal or add among the never-reaches; rule 8 logs the variable's ignoring once. -
Night decisions carried in (the writer, 2026-10-19): rules 10–12 proposed — the entity's model rule (OD-20261019-01), the queued swap's wait as a setting with 30 minutes proposed and a broken seat swapping at once (OD-20261019-03, with -02), and the minimum review labs at entity, project or team level, default 2, distinct from
review-count(OD-20261019-20) — each with Proof: none at the tag, and each mirrored by a planned row of §6 and of registries/policy-names.md. - Agreed Phase B review fold (2026-10-19): proposed model-rule rule 10 includes reading the status bar back under OD-20261019-01; it remains proposed, with no proof or implementation at the tag.
-
The gate-lift rename (the writer, 2026-10-19; OD-20261019-33, answer 3): the gate-lifting switch's name is
gate-liftfrom this revision — it lifts a gate, it is not a mode — andlocal-faststays the development mode's one name. Renamed here: §1's close, the Terms entry, the §3.3 catalogue row, rule 7's head and itsonreading, rule 8's governed value, §6's registry row. Renamed elsewhere, one word change per place: specs-draft/grants.md (the delegated-run line, the owner-only sentence, rule 5, open point 3), specs-draft/tasks-and-broker.md (the broker line), specs-draft/registry.md (rule 10's provenance mark; theprovenance.dev_modefield names of §3 stay), specs-draft/registries/operations.md (the--abandonrow), specs-draft/registries/policy-names.md (the switch's row), and Kernel amendment 02's text (eight places; its revision log lists them). The code's identifiers keep the tag's spelling —dev-modeinsrc/policy/types.ts,src/dev_mode.ts,DEV_MODE_OPERATIONS,TEAMCLI_DEV_MODE,provenance.dev_mode— as facts of v0.5.0; the port carries the echo. A wording ruling from the review then tightened the writing: the name carries its code pairing —gate-lift(in code:dev-mode, until the rename ships) — at the Terms entry, the catalogue row and the registry row, and the writing is generic (gate-lifting) where no identifier is cited; the code rename is the tool's own separate change, and the port does not wait for it.
Generated from docs/specs/policies.md at v0.5.2 (1d1224a4) — edit the repository, not this page.