@dtmd/temper 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,6 +49,22 @@ export interface Harness {
49
49
  readonly require: Readonly<Record<string, Requirement>>;
50
50
  /** The residual harness-level settings with no member home (a shrinking list). */
51
51
  readonly settings: Readonly<Record<string, unknown>>;
52
+ /**
53
+ * The **root member's own contract** — the clauses that bind to the whole governed
54
+ * forest rather than to one kind's population. Lowered to top-level clause rows
55
+ * carrying no `kind` column, the absence the engine reads as "the root's"
56
+ * (`specs/model/representation.md`, "The root member": the contract attaches to the
57
+ * harness the way it attaches to any member).
58
+ *
59
+ * Every clause here is judged at the selection grain, so the vocabulary is the
60
+ * selection predicates plus {@link reachable}; a member-grain clause names a field in
61
+ * a schema the root has no single kind to supply, and the gate refuses it at
62
+ * admissibility rather than leaving it unjudged.
63
+ *
64
+ * Absent, {@link rootDefaultContract} applies — the shipped floor, composed by
65
+ * {@link harness}.
66
+ */
67
+ readonly contract: readonly Clause[];
52
68
  /**
53
69
  * The root member's declared enforcement mode — harness-wide, overridable
54
70
  * per member. Defaults to `warn`: temper fabricates no enforcement the
@@ -69,4 +85,28 @@ export declare function harness(init: {
69
85
  require?: Readonly<Record<string, Requirement>>;
70
86
  settings?: Readonly<Record<string, unknown>>;
71
87
  mode?: EnforcementMode;
88
+ contract?: readonly Clause[];
72
89
  }): Harness;
90
+ /**
91
+ * The **shipped root default contract** — what the root member owes absent an authored
92
+ * one, and what the embedded built-in lock therefore carries for the stranger gate (a
93
+ * harness with no lock of its own gets the same root contract an emitted one does).
94
+ *
95
+ * Homed beside the root member's other fields, the precedent `dialDefaultContract` sets
96
+ * in `dial.ts`: a default contract lives with the surface it governs.
97
+ *
98
+ * Three clauses, all predicates only the root can bind: their selection is the whole
99
+ * forest and their judges read the reference graph, the committed lock and the discovery
100
+ * walk, none of which any one kind's population carries. `reachable` is the one check
101
+ * that catches authored configuration the harness never loads at all; `fresh` is the one
102
+ * that catches a projection or a fingerprinted source dependency that has moved out from
103
+ * under its lock row; `locus-declared` is the one that catches a document sitting at a
104
+ * governed locus that the program never declared.
105
+ *
106
+ * All advisory — today's posture for each, so no adopter turns red on the upgrade. A
107
+ * dead registration is often deliberate work-in-progress, a drifted projection is usually
108
+ * a re-emit away, and an undeclared document is often a surface mid-adoption; whether any
109
+ * of them gates is the adopting author's call, dialed or re-declared rather than
110
+ * tool-decided.
111
+ */
112
+ export declare const rootDefaultContract: readonly Clause[];
@@ -6,6 +6,7 @@
6
6
  * `temper.toml`, no roster/bindings dialect (the Decision rejects both);
7
7
  * composing partial harnesses is ordinary code.
8
8
  */
9
+ import { clause, fresh, locusDeclared, reachable } from "./contract.js";
9
10
  /**
10
11
  * Compose the harness from its six fields — ordinary code, Turing-completeness
11
12
  * quarantined at authoring time. Absent
@@ -20,5 +21,47 @@ export function harness(init) {
20
21
  require: init.require ?? {},
21
22
  settings: init.settings ?? {},
22
23
  mode: init.mode ?? "warn",
24
+ // The one place "a shipped root default applies when the author declares none" is
25
+ // spelled. Composing the array is the whole override surface: an authored
26
+ // `contract` replaces the default wholesale, never merges with it — the same
27
+ // rows-or-default rule a kind's `expect` binding takes over its floor.
28
+ contract: init.contract ?? rootDefaultContract,
23
29
  };
24
30
  }
31
+ /**
32
+ * The **shipped root default contract** — what the root member owes absent an authored
33
+ * one, and what the embedded built-in lock therefore carries for the stranger gate (a
34
+ * harness with no lock of its own gets the same root contract an emitted one does).
35
+ *
36
+ * Homed beside the root member's other fields, the precedent `dialDefaultContract` sets
37
+ * in `dial.ts`: a default contract lives with the surface it governs.
38
+ *
39
+ * Three clauses, all predicates only the root can bind: their selection is the whole
40
+ * forest and their judges read the reference graph, the committed lock and the discovery
41
+ * walk, none of which any one kind's population carries. `reachable` is the one check
42
+ * that catches authored configuration the harness never loads at all; `fresh` is the one
43
+ * that catches a projection or a fingerprinted source dependency that has moved out from
44
+ * under its lock row; `locus-declared` is the one that catches a document sitting at a
45
+ * governed locus that the program never declared.
46
+ *
47
+ * All advisory — today's posture for each, so no adopter turns red on the upgrade. A
48
+ * dead registration is often deliberate work-in-progress, a drifted projection is usually
49
+ * a re-emit away, and an undeclared document is often a surface mid-adoption; whether any
50
+ * of them gates is the adopting author's call, dialed or re-declared rather than
51
+ * tool-decided.
52
+ */
53
+ export const rootDefaultContract = [
54
+ clause(reachable(), {
55
+ severity: "advisory",
56
+ guidance: "This member is authored but unreachable: every registration channel its kind declares is provably dead, and no member that is reachable imports it. Claude never loads it, so it is context you maintain and never pay for — and a reader of the tree cannot tell it from live configuration. Three remedies: open a channel (give the `paths` globs a file they match, give the `description` trigger words), import it from a member that is reachable, or delete it. Advisory because a dead edge is a legitimate work-in-progress state; dial it or re-declare the clause `required` once your tree should hold the line.",
57
+ cite: "https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-15); https://code.claude.com/docs/en/skills (retrieved 2026-07-16)",
58
+ }),
59
+ clause(fresh(), {
60
+ severity: "advisory",
61
+ guidance: "A lock row this member owns no longer matches disk: either its committed projection was hand-edited, or a fingerprinted source dependency it imports or includes has moved. Nothing reverse-parses a projection back into the program, so the two sides stay apart until you reconcile them: edit the owning source and re-emit, and for a moved dependency re-verify the member's claims against the new bytes first. Advisory because a drifted checkout is usually one `emit` away and blocking every such run would be temper's escalation, not yours; dial this label to `required` — or re-declare the clause — once a drifted projection should fail CI.",
62
+ }),
63
+ clause(locusDeclared(), {
64
+ severity: "advisory",
65
+ guidance: "A document sits at a governed locus that your program declares no member for. Claude Code loads it, but temper maintains nothing about it: `emit` never writes or reaps it, `guard` never bound it, and every address under it resolves to nothing — so it reads as governed configuration while being governed by no one. Declare the member in the program and re-emit; for a directory whose documents are authored in place rather than projected, declare its kind `local` so `check` derives the rows at read time instead. Advisory because a tree mid-adoption legitimately carries documents the program has not reached yet; dial this label to `required` — or re-declare the clause — once every document at a governed locus should be one you declared.",
66
+ }),
67
+ ];
@@ -9,7 +9,7 @@
9
9
  * `@dtmd/temper/claude-code` subpath
10
10
  * — never from the root.
11
11
  */
12
- import type { KindDefinition } from "./kind.js";
12
+ import type { KindDefinition, Residue } from "./kind.js";
13
13
  import type { Prose } from "./prose.js";
14
14
  import type { Clause } from "./contract.js";
15
15
  /** A Claude Code skill — a directory whose entry file is `SKILL.md` with YAML frontmatter. */
@@ -766,6 +766,15 @@ export interface SettingsLocal {
766
766
  readonly autoMemoryEnabled?: boolean;
767
767
  /** Absolute or `~/`-prefixed path for auto-memory storage, honored at any settings scope (code.claude.com/docs/en/memory, retrieved 2026-07-26). */
768
768
  readonly autoMemoryDirectory?: string;
769
+ /**
770
+ * The opaque residue — every documented-but-untyped top-level key this overlay carries,
771
+ * projected flat and key-sorted after the typed fields above. The settings schema is
772
+ * large and version-evolving, so 0036 settles the unschematized remainder opaque and
773
+ * *named*: this bag is the name. The two halves partition the file's key space, which
774
+ * the bag's type holds: a key typed above belongs above and is unspellable here
775
+ * (code.claude.com/docs/en/settings, retrieved 2026-07-16).
776
+ */
777
+ readonly residue?: Residue<SettingsLocal>;
769
778
  }
770
779
  /**
771
780
  * `settings-local` — `.claude/settings.local.json`, a whole-file JSON document at the
@@ -773,9 +782,10 @@ export interface SettingsLocal {
773
782
  * target, its rows derived at read time and no row of it ever landing in the lock. Its
774
783
  * top-level keys are its fields; identity is the fixed singleton stem `settings.local` (the
775
784
  * `file` unit shape — every machine's overlay is the one file at this path, so no declared
776
- * key names it). Channel-less: machine configuration read by the harness, never surfaced to
777
- * the model (code.claude.com/docs/en/settings, retrieved 2026-07-16; decisions
778
- * 0032/0034/0036).
785
+ * key names it). The keys it does not type ride {@link SettingsLocal.residue}, the opaque
786
+ * channel 0036's partial governance names. Channel-less: machine configuration read by the
787
+ * harness, never surfaced to the model (code.claude.com/docs/en/settings, retrieved
788
+ * 2026-07-16; decisions 0032/0034/0036).
779
789
  */
780
790
  export declare const settingsLocal: KindDefinition<SettingsLocal>;
781
791
  /**
@@ -793,6 +803,102 @@ export declare const settingsLocal: KindDefinition<SettingsLocal>;
793
803
  * never a gate clause.
794
804
  */
795
805
  export declare const settingsLocalDefaultContract: readonly Clause[];
806
+ /**
807
+ * Claude Code's committed project settings — `.claude/settings.json`, the file a team
808
+ * checks in so everyone who clones the repository gets the same permissions, hooks,
809
+ * plugins and environment (code.claude.com/docs/en/settings, "Settings files and who they
810
+ * affect", retrieved 2026-09-22).
811
+ *
812
+ * Only the documented top-level keys a committed team file carries are typed here; the
813
+ * settings schema is large and version-evolving, and every key not named below survives as
814
+ * opaque residue named as such — the partial-governance posture `settings-local` already
815
+ * holds. What is deliberately absent is not residue but *ownership*: `hooks`,
816
+ * `enabledPlugins` and `extraKnownMarketplaces` are the collection addresses of the
817
+ * {@link hook}, {@link installedPlugin} and {@link knownMarketplace} kinds, so a member
818
+ * registers there and this container never types the key. Declaring one here would author
819
+ * the same segment twice.
820
+ */
821
+ export interface Settings {
822
+ /** JSON Schema URL for editor autocomplete; Claude Code ignores it at load time. */
823
+ readonly $schema?: string;
824
+ /** Tool-permission rules: `{ allow, ask, deny }`, each an array of rule strings, plus `defaultMode`. */
825
+ readonly permissions?: Readonly<Record<string, unknown>>;
826
+ /** Environment variables applied to every session and its subprocesses, a map of string to string. */
827
+ readonly env?: Readonly<Record<string, string>>;
828
+ /** The model this project runs as. */
829
+ readonly model?: string;
830
+ /** Attribution Claude Code adds to commits and pull requests: `{ commit, pr, sessionUrl }`. */
831
+ readonly attribution?: Readonly<Record<string, unknown>>;
832
+ /**
833
+ * Whether to add a Claude co-author trailer to git commits.
834
+ *
835
+ * @deprecated Claude Code documents `attribution` as this key's replacement
836
+ * (code.claude.com/docs/en/settings-reference#attribution, retrieved 2026-09-22).
837
+ */
838
+ readonly includeCoAuthoredBy?: boolean;
839
+ /** Whether project MCP servers from `.mcp.json` are auto-approved. */
840
+ readonly enableAllProjectMcpServers?: boolean;
841
+ /** MCP servers from `.mcp.json` to approve. */
842
+ readonly enabledMcpjsonServers?: readonly string[];
843
+ /** MCP servers from `.mcp.json` to reject. */
844
+ readonly disabledMcpjsonServers?: readonly string[];
845
+ /** The output rendering style. */
846
+ readonly outputStyle?: string;
847
+ /** Whether to enable auto-memory for this project. */
848
+ readonly autoMemoryEnabled?: boolean;
849
+ /** Absolute or `~/`-prefixed path for auto-memory storage, honored at any settings scope. */
850
+ readonly autoMemoryDirectory?: string;
851
+ /**
852
+ * The opaque residue — every documented-but-untyped top-level key the committed file
853
+ * carries, projected flat and key-sorted after the typed fields above. The settings
854
+ * reference documents several hundred keys and gains more, so the remainder is opaque
855
+ * and *named* rather than indicted; this bag is the name. The two halves partition the
856
+ * file's key space, which the bag's type holds: a key typed above belongs above and is
857
+ * unspellable here. Nor is it an escape hatch for the three collection addresses:
858
+ * `hooks`, `enabledPlugins` and `extraKnownMarketplaces` are their kinds' to author, so
859
+ * a key of those names does not belong here either
860
+ * (code.claude.com/docs/en/settings-reference, retrieved 2026-09-22).
861
+ */
862
+ readonly residue?: Residue<Settings>;
863
+ }
864
+ /**
865
+ * `settings` — `.claude/settings.json`, a whole-file JSON document at the **committed**
866
+ * commitment class: the program authors every key, `emit` renders the file whole, and the
867
+ * member's byte fingerprint makes a hand edit to any part of it drift under the root
868
+ * `fresh` clause (decision 0050). Its top-level keys are its fields; identity is the fixed singleton stem
869
+ * `settings` (the `file` unit shape — a project's committed settings are the one file at
870
+ * this documented path, so no declared key names it). Channel-less: configuration the
871
+ * harness reads, never surfaced to the model.
872
+ *
873
+ * It is the **container** of three registration collection addresses — `hooks`,
874
+ * `enabledPlugins`, `extraKnownMarketplaces` — which keep their own kinds
875
+ * ({@link hook}, {@link installedPlugin}, {@link knownMarketplace}). Those segments and
876
+ * this member's opaque residue are one file: emit renders the declared segments in
877
+ * address order, then the residue — which the member itself authors through
878
+ * {@link Settings.residue}, flat and key-sorted behind its typed fields
879
+ * (code.claude.com/docs/en/settings, retrieved 2026-09-22).
880
+ */
881
+ export declare const settings: KindDefinition<Settings>;
882
+ /**
883
+ * The default contract for `settings` — the structural container keys typed, everything
884
+ * else opaque. The settings reference documents several hundred keys, most of them scalar
885
+ * preferences and many of them managed-scope-only, so a `closedKeys()` allow-list would
886
+ * strand a valid committed file the moment upstream adds a key: the residue is opaque, not
887
+ * indicted. What stays decidable is the shape of the documented object-valued keys a
888
+ * committed team file carries — `permissions`, `env` and `attribution` are each a JSON
889
+ * *object*, and a value that is not one cannot be applied — so each is gated as a `map`.
890
+ *
891
+ * `hooks` is **not** gated here, and its absence is ownership rather than an opaque
892
+ * residue key: the committed file's `hooks` object is the {@link hook} kind's collection
893
+ * address, so its shape is that kind's contract to hold, member by member. This is the one
894
+ * place `settingsLocalDefaultContract`'s guidance does not transfer — a hook in the local
895
+ * overlay is opaque residue precisely because no kind reads it there.
896
+ *
897
+ * Deliberately absent as undecidable: whether a permission rule reads correctly, whether
898
+ * an env var is one this project needs, whether the chosen `model` exists — semantic
899
+ * judgment, never a gate clause.
900
+ */
901
+ export declare const settingsDefaultContract: readonly Clause[];
796
902
  /**
797
903
  * The default contract for `skill` — Anthropic's documented skill contract: the Agent
798
904
  * Skills open standard (agentskills.io), Anthropic's platform upload
@@ -620,9 +620,10 @@ export const marketplaceDefaultContract = [
620
620
  * target, its rows derived at read time and no row of it ever landing in the lock. Its
621
621
  * top-level keys are its fields; identity is the fixed singleton stem `settings.local` (the
622
622
  * `file` unit shape — every machine's overlay is the one file at this path, so no declared
623
- * key names it). Channel-less: machine configuration read by the harness, never surfaced to
624
- * the model (code.claude.com/docs/en/settings, retrieved 2026-07-16; decisions
625
- * 0032/0034/0036).
623
+ * key names it). The keys it does not type ride {@link SettingsLocal.residue}, the opaque
624
+ * channel 0036's partial governance names. Channel-less: machine configuration read by the
625
+ * harness, never surfaced to the model (code.claude.com/docs/en/settings, retrieved
626
+ * 2026-07-16; decisions 0032/0034/0036).
626
627
  */
627
628
  export const settingsLocal = kind({
628
629
  name: "settings-local",
@@ -662,6 +663,66 @@ export const settingsLocalDefaultContract = [
662
663
  cite: "https://code.claude.com/docs/en/settings#available-settings (retrieved 2026-07-16)",
663
664
  }),
664
665
  ];
666
+ /**
667
+ * `settings` — `.claude/settings.json`, a whole-file JSON document at the **committed**
668
+ * commitment class: the program authors every key, `emit` renders the file whole, and the
669
+ * member's byte fingerprint makes a hand edit to any part of it drift under the root
670
+ * `fresh` clause (decision 0050). Its top-level keys are its fields; identity is the fixed singleton stem
671
+ * `settings` (the `file` unit shape — a project's committed settings are the one file at
672
+ * this documented path, so no declared key names it). Channel-less: configuration the
673
+ * harness reads, never surfaced to the model.
674
+ *
675
+ * It is the **container** of three registration collection addresses — `hooks`,
676
+ * `enabledPlugins`, `extraKnownMarketplaces` — which keep their own kinds
677
+ * ({@link hook}, {@link installedPlugin}, {@link knownMarketplace}). Those segments and
678
+ * this member's opaque residue are one file: emit renders the declared segments in
679
+ * address order, then the residue — which the member itself authors through
680
+ * {@link Settings.residue}, flat and key-sorted behind its typed fields
681
+ * (code.claude.com/docs/en/settings, retrieved 2026-09-22).
682
+ */
683
+ export const settings = kind({
684
+ name: "settings",
685
+ locus: { kind: "at", root: ".claude", glob: "settings.json" },
686
+ format: "json-document",
687
+ unitShape: "file",
688
+ registration: [],
689
+ });
690
+ /**
691
+ * The default contract for `settings` — the structural container keys typed, everything
692
+ * else opaque. The settings reference documents several hundred keys, most of them scalar
693
+ * preferences and many of them managed-scope-only, so a `closedKeys()` allow-list would
694
+ * strand a valid committed file the moment upstream adds a key: the residue is opaque, not
695
+ * indicted. What stays decidable is the shape of the documented object-valued keys a
696
+ * committed team file carries — `permissions`, `env` and `attribution` are each a JSON
697
+ * *object*, and a value that is not one cannot be applied — so each is gated as a `map`.
698
+ *
699
+ * `hooks` is **not** gated here, and its absence is ownership rather than an opaque
700
+ * residue key: the committed file's `hooks` object is the {@link hook} kind's collection
701
+ * address, so its shape is that kind's contract to hold, member by member. This is the one
702
+ * place `settingsLocalDefaultContract`'s guidance does not transfer — a hook in the local
703
+ * overlay is opaque residue precisely because no kind reads it there.
704
+ *
705
+ * Deliberately absent as undecidable: whether a permission rule reads correctly, whether
706
+ * an env var is one this project needs, whether the chosen `model` exists — semantic
707
+ * judgment, never a gate clause.
708
+ */
709
+ export const settingsDefaultContract = [
710
+ clause(type("permissions", ["map"]), {
711
+ severity: "required",
712
+ guidance: "`permissions` is the tool-permission object — `allow`, `ask` and `deny` rule arrays plus `defaultMode`. A value that is not an object carries no rules Claude Code can read, so the rules a whole team relies on silently apply nothing.",
713
+ cite: "https://code.claude.com/docs/en/settings-reference#permissions (retrieved 2026-09-22)",
714
+ }),
715
+ clause(type("env", ["map"]), {
716
+ severity: "required",
717
+ guidance: "`env` is a map of environment variables — string keys to string values — applied to every session and its subprocesses. A non-object value is not a shorter spelling of one variable; it is a shape the loader cannot expand, so none of the variables take effect.",
718
+ cite: "https://code.claude.com/docs/en/settings-reference#env (retrieved 2026-09-22)",
719
+ }),
720
+ clause(type("attribution", ["map"]), {
721
+ severity: "required",
722
+ guidance: "`attribution` is the commit/PR attribution object — `commit`, `pr` and `sessionUrl`. A non-object value configures none of them, so Claude Code falls back to its default trailer with no error, which is exactly the outcome a project setting this key was avoiding.",
723
+ cite: "https://code.claude.com/docs/en/settings-reference#attribution (retrieved 2026-09-22)",
724
+ }),
725
+ ];
665
726
  /**
666
727
  * The default contract for `skill` — Anthropic's documented skill contract: the Agent
667
728
  * Skills open standard (agentskills.io), Anthropic's platform upload
@@ -7,7 +7,7 @@
7
7
  * specifier like any other. The built-in default contracts join the kinds here
8
8
  * too: adoption is `import { skill, skillDefaultContract } from "@dtmd/temper/claude-code"`.
9
9
  */
10
- export type { Agent, Hook, InstalledPlugin, KnownMarketplace, Marketplace, MarketplacePlugin, MarketplaceSource, McpServer, Memory, PluginManifest, Rule, SettingsLocal, Skill, SupportingDoc, } from "./builtins.js";
11
- export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
10
+ export type { Agent, Hook, InstalledPlugin, KnownMarketplace, Marketplace, MarketplacePlugin, MarketplaceSource, McpServer, Memory, PluginManifest, Rule, Settings, SettingsLocal, Skill, SupportingDoc, } from "./builtins.js";
11
+ export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settings, settingsDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
12
12
  export type { Blocks, File, Prose, Text } from "./prose.js";
13
13
  export { blocks, file, text } from "./prose.js";
@@ -7,5 +7,5 @@
7
7
  * specifier like any other. The built-in default contracts join the kinds here
8
8
  * too: adoption is `import { skill, skillDefaultContract } from "@dtmd/temper/claude-code"`.
9
9
  */
10
- export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
10
+ export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settings, settingsDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
11
11
  export { blocks, file, text } from "./prose.js";
@@ -40,8 +40,10 @@ export interface Predicate {
40
40
  * `incoming_min`/`incoming_max`/`outgoing_min`/`outgoing_max`). */
41
41
  readonly args?: Readonly<Record<string, number>>;
42
42
  /**
43
- * `membership`'s target requirement name a separate slot from `field` (the
44
- * checked field) since `membership` names both.
43
+ * The **requirement name** a clause reads its source set from `membership`'s
44
+ * allowed-value target and `reached-from`'s closure roots, one naming scheme for
45
+ * both. A separate slot from `field` since `membership` names a checked field too,
46
+ * and `reached-from` names no field at all.
45
47
  */
46
48
  readonly target?: string;
47
49
  /**
@@ -79,6 +81,13 @@ export interface Predicate {
79
81
  };
80
82
  /** `require_sections`'s required heading list. */
81
83
  readonly sections?: readonly string[];
84
+ /**
85
+ * A by-incidence clause's **field set** — the fields whose edges the selection is
86
+ * filtered to. `degree`'s bound filter and `reached-from`'s via set both ride it:
87
+ * one concept, one slot, mirroring the lock's own shared `fields` column rather than
88
+ * a `degree`-private key. Absent ⇒ unfiltered, every edge at the member.
89
+ */
90
+ readonly fields?: readonly string[];
82
91
  /** `extent`'s declared unit — the render-side size proxy the bound is measured in. */
83
92
  readonly unit?: ExtentUnit;
84
93
  }
@@ -185,6 +194,56 @@ export declare const globValid: (field: string) => Predicate;
185
194
  * wrong must not block.
186
195
  */
187
196
  export declare const mentionReachable: (scopeField: string, gateField: string) => Predicate;
197
+ /**
198
+ * Every selected member is reachable from the world — its own inbound registration edge
199
+ * is live, or a reachable member imports it. A member whose every declared registration
200
+ * channel is provably dead (a `paths` glob set matching no file, a blank
201
+ * description-trigger field) and that no live importer reaches is authored configuration
202
+ * the harness never loads.
203
+ *
204
+ * Names **no field**: the grain is each-grain over the *root* selection — the whole
205
+ * governed forest — and the verdict is read off the reference graph's reachability
206
+ * closure, never off any member's own fields. So it composes into a root `contract`,
207
+ * where a member-grain clause is refused: there is no one kind whose schema a root
208
+ * clause could name a field in.
209
+ */
210
+ export declare const reachable: () => Predicate;
211
+ /**
212
+ * Every lock row a selected member owns still matches disk — its projection's byte
213
+ * fingerprint, and each fingerprinted source dependency a layout import or a
214
+ * composed-prose include recorded. A drifted row means the authored source changed and
215
+ * `emit` has not run, or the emitted bytes were hand-edited.
216
+ *
217
+ * Drift is one comparison in one vocabulary, so one predicate carries all of it: a
218
+ * hand-edited projection, a moved import target and a moved include target are the same
219
+ * freshness fact over the same rows, and hardening freshness hardens it once.
220
+ *
221
+ * Names **no field**, like {@link reachable}: the argument is the committed lock read
222
+ * against disk, never any member's own fields. So it composes into a root `contract`,
223
+ * whose selection is the whole governed forest — a per-kind binding beside the root's
224
+ * would double-report one row, and a user-declared kind carries no default contract to
225
+ * bind it in.
226
+ */
227
+ export declare const fresh: () => Predicate;
228
+ /**
229
+ * Every document discovery finds at a **governed locus** is one the lock declares a
230
+ * member for — a file-content kind's committed document joined against the provenance
231
+ * rows, a layout host's document against the `layout_source` record `emit` writes when
232
+ * it reads one. A stranger at a governed locus is loaded by Claude Code and maintained
233
+ * by nobody: `emit` never reaps it, `guard` never bound it, and every address under it
234
+ * resolves to nothing.
235
+ *
236
+ * Kept apart from {@link fresh} rather than folded into it: a stale pin and an
237
+ * undeclared document are different facts with different remedies — re-emit versus
238
+ * declare the member — and a read-only ground kind draws the second routinely while
239
+ * its pins stay fresh, so hardening one must not harden the other.
240
+ *
241
+ * Names **no field**, like {@link fresh}: the subject is the discovery walk read
242
+ * against the lock's declarations, never any member's own fields. So it composes into a
243
+ * root `contract` — what is governed is a locus, not a population, and a per-kind
244
+ * binding would have no member to hang off.
245
+ */
246
+ export declare const locusDeclared: () => Predicate;
188
247
  /**
189
248
  * Every edge the member's kind declares is placed by the format that renders the member
190
249
  * — a format that omits one renders a contract the prose does not represent. Names no
@@ -202,7 +261,16 @@ export declare const count: (bounds: {
202
261
  export declare const unique: (field: string) => Predicate;
203
262
  /** Every selected member's `field` value is drawn from a feature over the selection `target` declares. */
204
263
  export declare const membership: (field: string, target: string) => Predicate;
205
- /** The in/out edge-count bound every selected member must land in. At least one direction must be given. */
264
+ /**
265
+ * The in/out edge-count bound every selected member must land in. At least one
266
+ * direction must be given.
267
+ *
268
+ * `fields` filters the by-incidence selection both bounds range over — the clause's
269
+ * own filter, not either direction's. Omit it and the bound ranges over every edge at
270
+ * the member; name a set and it ranges over the union of those fields' edges, so two
271
+ * fields to one target count two. Containment (`contains:<kind>`) counts only where a
272
+ * filter names it.
273
+ */
206
274
  export declare const degree: (bounds: {
207
275
  incoming?: {
208
276
  min?: number;
@@ -212,7 +280,32 @@ export declare const degree: (bounds: {
212
280
  min?: number;
213
281
  max?: number;
214
282
  };
283
+ fields?: readonly string[];
215
284
  }) => Predicate;
285
+ /**
286
+ * Every selected member lies in the forward closure of `roots` over the `via` field
287
+ * set — `degree`'s global counterpart. `degree` asks the local question (how many arcs
288
+ * sit at this member), so the first orphan of a dead chain fires and none behind it;
289
+ * this asks the global one, so every member the roots cannot reach is equally a
290
+ * finding.
291
+ *
292
+ * `roots` names a **requirement**, spelled exactly as `membership` names its target
293
+ * set: rootness is a role, and a role is opt-in, so the roots are declared once and
294
+ * never re-derived from a kind. A root holds trivially, and the walk visits each node
295
+ * once, so a cycle inside the closure terminates and moves no verdict.
296
+ *
297
+ * `via` is the same by-incidence field set `degree`'s filter rides. Omit it and the
298
+ * closure follows every edge at a member but containment; name a set and it follows
299
+ * the union of those fields' arcs, so a mention or an import counts only where the set
300
+ * names its field.
301
+ *
302
+ * Each-grain over the **selection** the clause binds to, and judged by the reference
303
+ * graph rather than any member's own fields. It belongs in **no default contract**: a
304
+ * clause demanding every member be reached is the declaration-density demand
305
+ * `specs/intent.md`'s invariant 1 forbids unless the author declares it
306
+ * (`specs/decisions/0056-reached-from-joins-the-vocabulary.md`).
307
+ */
308
+ export declare const reachedFrom: (roots: string, via?: readonly string[]) => Predicate;
216
309
  /**
217
310
  * A guarded clause — a predicate (restricted to `type` or `enum`) that acts as a
218
311
  * guard, with a body of ordinary clauses that fire only where the guard holds.
@@ -130,6 +130,56 @@ export const mentionReachable = (scopeField, gateField) => ({
130
130
  field: scopeField,
131
131
  gate: gateField,
132
132
  });
133
+ /**
134
+ * Every selected member is reachable from the world — its own inbound registration edge
135
+ * is live, or a reachable member imports it. A member whose every declared registration
136
+ * channel is provably dead (a `paths` glob set matching no file, a blank
137
+ * description-trigger field) and that no live importer reaches is authored configuration
138
+ * the harness never loads.
139
+ *
140
+ * Names **no field**: the grain is each-grain over the *root* selection — the whole
141
+ * governed forest — and the verdict is read off the reference graph's reachability
142
+ * closure, never off any member's own fields. So it composes into a root `contract`,
143
+ * where a member-grain clause is refused: there is no one kind whose schema a root
144
+ * clause could name a field in.
145
+ */
146
+ export const reachable = () => ({ key: "reachable" });
147
+ /**
148
+ * Every lock row a selected member owns still matches disk — its projection's byte
149
+ * fingerprint, and each fingerprinted source dependency a layout import or a
150
+ * composed-prose include recorded. A drifted row means the authored source changed and
151
+ * `emit` has not run, or the emitted bytes were hand-edited.
152
+ *
153
+ * Drift is one comparison in one vocabulary, so one predicate carries all of it: a
154
+ * hand-edited projection, a moved import target and a moved include target are the same
155
+ * freshness fact over the same rows, and hardening freshness hardens it once.
156
+ *
157
+ * Names **no field**, like {@link reachable}: the argument is the committed lock read
158
+ * against disk, never any member's own fields. So it composes into a root `contract`,
159
+ * whose selection is the whole governed forest — a per-kind binding beside the root's
160
+ * would double-report one row, and a user-declared kind carries no default contract to
161
+ * bind it in.
162
+ */
163
+ export const fresh = () => ({ key: "fresh" });
164
+ /**
165
+ * Every document discovery finds at a **governed locus** is one the lock declares a
166
+ * member for — a file-content kind's committed document joined against the provenance
167
+ * rows, a layout host's document against the `layout_source` record `emit` writes when
168
+ * it reads one. A stranger at a governed locus is loaded by Claude Code and maintained
169
+ * by nobody: `emit` never reaps it, `guard` never bound it, and every address under it
170
+ * resolves to nothing.
171
+ *
172
+ * Kept apart from {@link fresh} rather than folded into it: a stale pin and an
173
+ * undeclared document are different facts with different remedies — re-emit versus
174
+ * declare the member — and a read-only ground kind draws the second routinely while
175
+ * its pins stay fresh, so hardening one must not harden the other.
176
+ *
177
+ * Names **no field**, like {@link fresh}: the subject is the discovery walk read
178
+ * against the lock's declarations, never any member's own fields. So it composes into a
179
+ * root `contract` — what is governed is a locus, not a population, and a per-kind
180
+ * binding would have no member to hang off.
181
+ */
182
+ export const locusDeclared = () => ({ key: "locus-declared" });
133
183
  /**
134
184
  * Every edge the member's kind declares is placed by the format that renders the member
135
185
  * — a format that omits one renders a contract the prose does not represent. Names no
@@ -161,7 +211,16 @@ export const membership = (field, target) => ({
161
211
  field,
162
212
  target,
163
213
  });
164
- /** The in/out edge-count bound every selected member must land in. At least one direction must be given. */
214
+ /**
215
+ * The in/out edge-count bound every selected member must land in. At least one
216
+ * direction must be given.
217
+ *
218
+ * `fields` filters the by-incidence selection both bounds range over — the clause's
219
+ * own filter, not either direction's. Omit it and the bound ranges over every edge at
220
+ * the member; name a set and it ranges over the union of those fields' edges, so two
221
+ * fields to one target count two. Containment (`contains:<kind>`) counts only where a
222
+ * filter names it.
223
+ */
165
224
  export const degree = (bounds) => {
166
225
  const args = {};
167
226
  if (bounds.incoming?.min !== undefined)
@@ -172,8 +231,36 @@ export const degree = (bounds) => {
172
231
  args.outgoing_min = bounds.outgoing.min;
173
232
  if (bounds.outgoing?.max !== undefined)
174
233
  args.outgoing_max = bounds.outgoing.max;
175
- return { key: "degree", args };
234
+ return bounds.fields === undefined
235
+ ? { key: "degree", args }
236
+ : { key: "degree", args, fields: bounds.fields };
176
237
  };
238
+ /**
239
+ * Every selected member lies in the forward closure of `roots` over the `via` field
240
+ * set — `degree`'s global counterpart. `degree` asks the local question (how many arcs
241
+ * sit at this member), so the first orphan of a dead chain fires and none behind it;
242
+ * this asks the global one, so every member the roots cannot reach is equally a
243
+ * finding.
244
+ *
245
+ * `roots` names a **requirement**, spelled exactly as `membership` names its target
246
+ * set: rootness is a role, and a role is opt-in, so the roots are declared once and
247
+ * never re-derived from a kind. A root holds trivially, and the walk visits each node
248
+ * once, so a cycle inside the closure terminates and moves no verdict.
249
+ *
250
+ * `via` is the same by-incidence field set `degree`'s filter rides. Omit it and the
251
+ * closure follows every edge at a member but containment; name a set and it follows
252
+ * the union of those fields' arcs, so a mention or an import counts only where the set
253
+ * names its field.
254
+ *
255
+ * Each-grain over the **selection** the clause binds to, and judged by the reference
256
+ * graph rather than any member's own fields. It belongs in **no default contract**: a
257
+ * clause demanding every member be reached is the declaration-density demand
258
+ * `specs/intent.md`'s invariant 1 forbids unless the author declares it
259
+ * (`specs/decisions/0056-reached-from-joins-the-vocabulary.md`).
260
+ */
261
+ export const reachedFrom = (roots, via) => via === undefined
262
+ ? { key: "reached-from", target: roots }
263
+ : { key: "reached-from", target: roots, fields: via };
177
264
  /**
178
265
  * A guarded clause — a predicate (restricted to `type` or `enum`) that acts as a
179
266
  * guard, with a body of ordinary clauses that fire only where the guard holds.