unknown-knowledge 2.1.0
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.
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +167 -0
- package/cli/.gitkeep +0 -0
- package/cli/commands/init-copy.js +90 -0
- package/cli/commands/init.js +386 -0
- package/cli/init-copy.js +24 -0
- package/cli/init.js +24 -0
- package/cli/kit.manifest.yaml +256 -0
- package/cli/lib/copy-payload.js +334 -0
- package/cli/lib/generate-wrappers.js +162 -0
- package/package.json +36 -0
- package/payload/adapter-fixtures/README.md +53 -0
- package/payload/adapter-fixtures/html/EXPECTED.yaml +50 -0
- package/payload/adapter-fixtures/html/sample.html +38 -0
- package/payload/adapter-fixtures/md/EXPECTED.yaml +65 -0
- package/payload/adapter-fixtures/md/sample.md +32 -0
- package/payload/adapter-fixtures/pdf/EXPECTED.yaml +45 -0
- package/payload/adapter-fixtures/pdf/sample.pdf +0 -0
- package/payload/adapter-fixtures/txt/EXPECTED.yaml +31 -0
- package/payload/adapter-fixtures/txt/sample.txt +18 -0
- package/payload/docs/README.md +102 -0
- package/payload/docs/boundaries.md +60 -0
- package/payload/docs/ci-wiring.md +109 -0
- package/payload/docs/steward-guide.md +238 -0
- package/payload/engine/audit.js +34 -0
- package/payload/engine/commands/audit.js +289 -0
- package/payload/engine/commands/derive.js +334 -0
- package/payload/engine/commands/ingest.js +124 -0
- package/payload/engine/commands/log-entry.js +85 -0
- package/payload/engine/commands/phoenix.js +206 -0
- package/payload/engine/commands/preflight.js +530 -0
- package/payload/engine/commands/resolve.js +1678 -0
- package/payload/engine/commands/survey-map.js +351 -0
- package/payload/engine/commands/validate-values.js +315 -0
- package/payload/engine/commands/validate.js +1426 -0
- package/payload/engine/derive.js +34 -0
- package/payload/engine/ingest.js +34 -0
- package/payload/engine/lib/anchor-signatures.js +126 -0
- package/payload/engine/lib/boot.js +39 -0
- package/payload/engine/lib/call-numbers.js +133 -0
- package/payload/engine/lib/cli.js +147 -0
- package/payload/engine/lib/coverage.js +849 -0
- package/payload/engine/lib/decomposition.js +225 -0
- package/payload/engine/lib/derived.js +494 -0
- package/payload/engine/lib/engine-refusal.js +40 -0
- package/payload/engine/lib/exit-codes.js +14 -0
- package/payload/engine/lib/extractor-kinds.js +955 -0
- package/payload/engine/lib/format-adapters.js +802 -0
- package/payload/engine/lib/id-grammars.js +178 -0
- package/payload/engine/lib/iso-date.js +55 -0
- package/payload/engine/lib/kit-root.js +101 -0
- package/payload/engine/lib/load-stores.js +1624 -0
- package/payload/engine/lib/log-entry.js +196 -0
- package/payload/engine/lib/phoenix.js +628 -0
- package/payload/engine/lib/scoring.js +150 -0
- package/payload/engine/lib/suppressions.js +172 -0
- package/payload/engine/lib/time-verdicts.js +282 -0
- package/payload/engine/lib/usage-error.js +14 -0
- package/payload/engine/lib/validate-record.js +504 -0
- package/payload/engine/log-entry.js +34 -0
- package/payload/engine/phoenix.js +39 -0
- package/payload/engine/preflight.js +34 -0
- package/payload/engine/resolve.js +34 -0
- package/payload/engine/survey-map.js +34 -0
- package/payload/engine/validate-values.js +34 -0
- package/payload/engine/validate.js +34 -0
- package/payload/extractor-fixtures/.gitkeep +0 -0
- package/payload/extractor-fixtures/README.md +29 -0
- package/payload/extractor-fixtures/swift/strings-keys/EXPECTED.yaml +8 -0
- package/payload/extractor-fixtures/swift/strings-keys/sample.strings +15 -0
- package/payload/extractor-fixtures/swift/swift-const-array/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/swift/swift-const-array/sample.swift +21 -0
- package/payload/extractor-fixtures/swift/swift-enum/EXPECTED.yaml +8 -0
- package/payload/extractor-fixtures/swift/swift-enum/sample.swift +30 -0
- package/payload/extractor-fixtures/swift/yaml-keys/EXPECTED.yaml +6 -0
- package/payload/extractor-fixtures/swift/yaml-keys/sample.yaml +23 -0
- package/payload/extractor-fixtures/swift/yaml-map-keys/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/swift/yaml-map-keys/sample.yaml +15 -0
- package/payload/extractor-fixtures/ts/dir-modules/EXPECTED.yaml +12 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/alpha.widget.ts +1 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/beta.widget.ts +1 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/gamma.widget.ts +1 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/helpers/format.ts +3 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/widgets.test.ts +2 -0
- package/payload/extractor-fixtures/ts/json-keys/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/ts/json-keys/sample.json +5 -0
- package/payload/extractor-fixtures/ts/json-map-keys/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/ts/json-map-keys/sample.json +13 -0
- package/payload/extractor-fixtures/ts/ts-const-array/EXPECTED.yaml +6 -0
- package/payload/extractor-fixtures/ts/ts-const-array/sample.ts +13 -0
- package/payload/extractor-fixtures/ts/ts-enum/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/ts/ts-enum/sample.ts +11 -0
- package/payload/extractor-fixtures/ts/ts-object-keys/EXPECTED.yaml +6 -0
- package/payload/extractor-fixtures/ts/ts-object-keys/sample.tsx +23 -0
- package/payload/extractor-fixtures/ts/ts-union/EXPECTED.yaml +5 -0
- package/payload/extractor-fixtures/ts/ts-union/sample.ts +9 -0
- package/payload/hooks/pre-commit +37 -0
- package/payload/hooks/reverse-lookup +66 -0
- package/payload/package.json +3 -0
- package/payload/protocol/.gitkeep +0 -0
- package/payload/protocol/AGENTS.md +239 -0
- package/payload/protocol/derived-layer.md +174 -0
- package/payload/protocol/new-kind-pipeline.md +179 -0
- package/payload/protocol/registry-warrant.md +162 -0
- package/payload/protocol/skills/kb-build.md +303 -0
- package/payload/protocol/skills/knowledge-audit.md +183 -0
- package/payload/protocol/skills/knowledge-bootstrap.md +229 -0
- package/payload/protocol/skills/knowledge-reflect.md +397 -0
- package/payload/schemas/catalog.schema.json +32 -0
- package/payload/schemas/decision-entry.schema.json +122 -0
- package/payload/schemas/finding.schema.json +77 -0
- package/payload/schemas/gap.schema.json +52 -0
- package/payload/schemas/graduation-categories.schema.json +64 -0
- package/payload/schemas/knowledge-leaf.schema.json +194 -0
- package/payload/schemas/miss.schema.json +45 -0
- package/payload/schemas/ontology-concept.schema.json +115 -0
- package/payload/schemas/phoenix-event.schema.json +76 -0
- package/payload/schemas/registry.schema.json +57 -0
- package/payload/schemas/rules.schema.json +14 -0
- package/payload/schemas/survey-scope.schema.json +23 -0
- package/payload/templates/decisions/_catalog.yaml +7 -0
- package/payload/templates/decisions/_registries/graduation-categories.yaml +42 -0
- package/payload/templates/decisions/phoenix-event.yaml +74 -0
- package/payload/templates/decisions/reflect-mint-proposal.yaml +100 -0
- package/payload/templates/decisions/registry-minting.yaml +58 -0
- package/payload/templates/decisions/trust-graduation.yaml +120 -0
- package/payload/templates/decisions/trust-revocation.yaml +106 -0
- package/payload/templates/knowledge/_catalog.yaml +9 -0
- package/payload/templates/knowledge/_registries/anchor.yaml +42 -0
- package/payload/templates/knowledge/_registries/authority-tiers.yaml +32 -0
- package/payload/templates/knowledge/_registries/domains.yaml +43 -0
- package/payload/templates/knowledge/_registries/form.yaml +38 -0
- package/payload/templates/knowledge/_registries/jurisdictions.yaml +20 -0
- package/payload/templates/knowledge/_registries/operations.yaml +18 -0
- package/payload/templates/knowledge/_registries/stage.yaml +53 -0
- package/payload/templates/knowledge/_rules.yaml +6 -0
- package/payload/templates/new-kind/README.md +107 -0
- package/payload/templates/new-kind/descriptor.example.yaml +18 -0
- package/payload/templates/new-kind/fixture/EXPECTED.yaml +6 -0
- package/payload/templates/new-kind/fixture/demo-anchor.list +2 -0
- package/payload/templates/new-kind/fixture/sample.list +7 -0
- package/payload/templates/new-kind/parser.example.js +98 -0
- package/payload/templates/ontology/_catalog.yaml +6 -0
- package/payload/templates/ontology/_rules.yaml +6 -0
- package/payload/wrappers/cursor.mdc +15 -0
- package/payload/wrappers/pointer.md +10 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Id grammars (UCS-1142) — one module, one grammar per id space.
|
|
3
|
+
*
|
|
4
|
+
* Every store mints ids in its own shape (§3.5): concepts K-NNN, knowledge
|
|
5
|
+
* leaves dotted notation, decisions D-NNN or a provisional D-YYYY-MM-DD-slug.
|
|
6
|
+
* That shape is checked in two places that must never disagree — the shipped
|
|
7
|
+
* JSON Schemas validate record-level ids, and the structural validator's
|
|
8
|
+
* id-shape check validates catalog rows (plain strings there by design, since
|
|
9
|
+
* a catalog row is a pointer, not a record). It used to be spelled five times:
|
|
10
|
+
* four copy-pasted `$defs/notation` blocks plus a regex literal in the
|
|
11
|
+
* validator, whose human-facing hint travelled separately from the pattern it
|
|
12
|
+
* described. A grammar declared five times is five chances to change four.
|
|
13
|
+
*
|
|
14
|
+
* Here the pattern and the hint are ONE entry, and both consumers read it:
|
|
15
|
+
*
|
|
16
|
+
* - validate-record.js injects `pattern` into each schema's `$defs` as the
|
|
17
|
+
* schema documents load, so the engine validates against this module even
|
|
18
|
+
* though the JSON files carry their own copy (see SCHEMA_DEFS below);
|
|
19
|
+
* - commands/validate.js compiles `pattern` for the id-shape check and
|
|
20
|
+
* quotes `hint` in the finding message.
|
|
21
|
+
*
|
|
22
|
+
* The pattern is stored as a STRING, not a RegExp: JSON Schema's `pattern`
|
|
23
|
+
* keyword takes a string, and the validator compiles what it needs. One
|
|
24
|
+
* spelling serves both, so the two can never drift apart.
|
|
25
|
+
*
|
|
26
|
+
* Adding an id space is this module plus its consumers' existing lookups —
|
|
27
|
+
* no schema edit, no second copy (tests/id-grammars.test.js pins that).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const ontology = Object.freeze({
|
|
31
|
+
pattern: '^K-[0-9]+$',
|
|
32
|
+
hint: 'K-NNN',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The LEGACY dotted notation (UCS-1147) — a leaf's optional display label.
|
|
37
|
+
*
|
|
38
|
+
* Still a grammar, because the field is still validated when present: a
|
|
39
|
+
* malformed notation is a defect whether or not anything treats it as
|
|
40
|
+
* identity. What it is no longer is an id space anything RESOLVES through —
|
|
41
|
+
* `leaf-ref` does not accept it, and no leaf is indexed by it. The hint says
|
|
42
|
+
* "legacy" out loud, so a finding quoting it cannot read as an invitation to
|
|
43
|
+
* cite this way.
|
|
44
|
+
*/
|
|
45
|
+
const knowledge = Object.freeze({
|
|
46
|
+
pattern: '^[0-9]+(\\.[0-9]+)*$',
|
|
47
|
+
hint: 'legacy dotted notation, e.g. "362.1"',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const decisions = Object.freeze({
|
|
51
|
+
pattern: '^D-([0-9]+|[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9-]+)$',
|
|
52
|
+
hint: 'D-NNN or provisional D-YYYY-MM-DD-slug',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A leaf's accession id (UCS-1144) — opaque, minted at PR time, never reused,
|
|
57
|
+
* never positional. Six digits is the mint width the K-/D- conventions imply
|
|
58
|
+
* read at library scale; it is FIXED, unlike K-NNN, because an accession
|
|
59
|
+
* carries no structure to grow into: L-000001 and L-1 would be two spellings
|
|
60
|
+
* of one identity, and "never reused" cannot survive two spellings.
|
|
61
|
+
*
|
|
62
|
+
* Since UCS-1147 this is a leaf's REQUIRED identity and the only shape a
|
|
63
|
+
* citation of a leaf may take.
|
|
64
|
+
*/
|
|
65
|
+
const accessions = Object.freeze({
|
|
66
|
+
pattern: '^L-[0-9]{6}$',
|
|
67
|
+
// Says what to write AND why the field is there, because the reader of this
|
|
68
|
+
// hint is most often an author whose leaf predates the contract: the
|
|
69
|
+
// `missing-required` on a leaf's `id` quotes it, and "L-NNNNNN" alone would
|
|
70
|
+
// tell them the shape of a field without saying that it is now the leaf's
|
|
71
|
+
// identity or that assigning one is the migration.
|
|
72
|
+
hint: 'an accession id of the form L-NNNNNN — every leaf mints one as its identity (UCS-1147)',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What a finding says to an author who cited a leaf the retired way.
|
|
77
|
+
*
|
|
78
|
+
* Lives beside the grammars because it is the human half of the same fact: the
|
|
79
|
+
* pattern says which spellings are legal, and this says what to do about the
|
|
80
|
+
* one that no longer is. Both travel together into every message that quotes
|
|
81
|
+
* them, for the reason this module exists — a hint that drifted from its
|
|
82
|
+
* pattern would send an author to the wrong edit.
|
|
83
|
+
*/
|
|
84
|
+
export const ACCESSION_MIGRATION_HINT =
|
|
85
|
+
'the leaf\'s accession id (L-NNNNNN); the dotted notation is a legacy display '
|
|
86
|
+
+ 'label and no longer resolves as a citation';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Store → the id grammar its ids obey (§3.5).
|
|
90
|
+
*
|
|
91
|
+
* `pattern` is an anchored JSON-Schema-flavored regex source; `hint` is the
|
|
92
|
+
* human-facing shape quoted back in findings, and travels WITH the pattern so
|
|
93
|
+
* a grammar change cannot leave the prose describing the old one.
|
|
94
|
+
*
|
|
95
|
+
* @type {Readonly<Record<string, { pattern: string, hint: string }>>}
|
|
96
|
+
*/
|
|
97
|
+
export const ID_GRAMMARS = Object.freeze({
|
|
98
|
+
ontology,
|
|
99
|
+
knowledge,
|
|
100
|
+
decisions,
|
|
101
|
+
accessions,
|
|
102
|
+
/**
|
|
103
|
+
* What a CITATION of a leaf may look like: an accession, and nothing else
|
|
104
|
+
* (UCS-1147's contract phase).
|
|
105
|
+
*
|
|
106
|
+
* It was a UNION of accession and notation while both spellings were legal
|
|
107
|
+
* (UCS-1144's expand phase), and it narrowed here exactly as that ticket
|
|
108
|
+
* predicted — the migrate batches (UCS-1145, UCS-1146) rewrote every
|
|
109
|
+
* notation-form citation first, so narrowing breaks nothing that was left.
|
|
110
|
+
*
|
|
111
|
+
* It stays a named entry rather than collapsing into `accessions` at every
|
|
112
|
+
* call site, and that is deliberate: `accessions` says what a leaf MINTS and
|
|
113
|
+
* `leaf-ref` says what a record may CITE. Those are two claims that happen to
|
|
114
|
+
* coincide today, and consumers read them by name for the same reason they
|
|
115
|
+
* always did — the day they diverge again is a change to this table, not a
|
|
116
|
+
* sweep through the surfaces that judge citations. The `hint` differs for
|
|
117
|
+
* that reason too: a citation finding names the migration, a minting finding
|
|
118
|
+
* names the shape.
|
|
119
|
+
*/
|
|
120
|
+
'leaf-ref': Object.freeze({
|
|
121
|
+
pattern: accessions.pattern,
|
|
122
|
+
hint: ACCESSION_MIGRATION_HINT,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Which `$defs` name in the shipped schemas each id space owns.
|
|
128
|
+
*
|
|
129
|
+
* The JSON files keep their own `$defs` copies so they stay self-contained for
|
|
130
|
+
* external tools (a cross-file `$ref` is outside the keyword subset the engine
|
|
131
|
+
* interprets, and would break every consumer that reads one schema alone).
|
|
132
|
+
* This map is how the copies stop being a second source of truth: the loader
|
|
133
|
+
* OVERWRITES each named def's pattern with the one above, so a JSON file that
|
|
134
|
+
* drifts is corrected at load rather than silently believed.
|
|
135
|
+
*
|
|
136
|
+
* Only spaces whose grammar appears in a schema `$defs` are listed —
|
|
137
|
+
* ontology/decisions ids reach the schemas through conceptRef/decisionRef and
|
|
138
|
+
* are not part of the leaf-notation seam this ticket settles.
|
|
139
|
+
*
|
|
140
|
+
* The three leaf entries stay three defs after UCS-1147 narrowed citations to
|
|
141
|
+
* accessions, and the reason is what they each say rather than what they each
|
|
142
|
+
* match. `accession` is a leaf's identity — required, and what `leafRef` cites.
|
|
143
|
+
* `notation` is the optional LEGACY display label, still dotted-only and still
|
|
144
|
+
* validated when present, because a malformed label is a defect even though
|
|
145
|
+
* nothing resolves through it. `leafRef` and `accession` carry the same pattern
|
|
146
|
+
* today; collapsing them would lose the distinction between what a leaf mints
|
|
147
|
+
* and what a record may cite, which is exactly the distinction that let the
|
|
148
|
+
* expand phase widen citations without ever widening leaf notation itself.
|
|
149
|
+
*
|
|
150
|
+
* @type {Readonly<Record<string, string>>}
|
|
151
|
+
*/
|
|
152
|
+
export const SCHEMA_DEFS = Object.freeze({
|
|
153
|
+
knowledge: 'notation',
|
|
154
|
+
'leaf-ref': 'leafRef',
|
|
155
|
+
accessions: 'accession',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The compiled matcher for an id space. Anchored by construction (the patterns
|
|
160
|
+
* above carry their own ^/$), cached per space so the hot id-shape loop does
|
|
161
|
+
* not recompile.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} space one of the keys of ID_GRAMMARS
|
|
164
|
+
* @returns {RegExp} the compiled grammar
|
|
165
|
+
*/
|
|
166
|
+
const compiled = new Map();
|
|
167
|
+
export function idPattern(space) {
|
|
168
|
+
let re = compiled.get(space);
|
|
169
|
+
if (!re) {
|
|
170
|
+
const grammar = ID_GRAMMARS[space];
|
|
171
|
+
if (!grammar) {
|
|
172
|
+
throw new TypeError(`unknown id space "${space}" (expected one of: ${Object.keys(ID_GRAMMARS).join(', ')})`);
|
|
173
|
+
}
|
|
174
|
+
re = new RegExp(grammar.pattern);
|
|
175
|
+
compiled.set(space, re);
|
|
176
|
+
}
|
|
177
|
+
return re;
|
|
178
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What "an ISO date" means, once (UCS-957).
|
|
3
|
+
*
|
|
4
|
+
* The rule was stated four times — in `lib/log-entry.js`, `lib/suppressions.js`,
|
|
5
|
+
* `commands/preflight.js` and `commands/audit.js` — and the copies disagreed.
|
|
6
|
+
* Three checked the SHAPE. Only preflight also asked the calendar.
|
|
7
|
+
*
|
|
8
|
+
* `Date.parse('2026-02-30')` does not fail. It rolls forward to March 2nd. So
|
|
9
|
+
* `audit --today 2026-02-30` exited 0 and reported staleness measured from a
|
|
10
|
+
* day that does not exist, two days later than the caller named, while
|
|
11
|
+
* preflight refused the same string outright.
|
|
12
|
+
*
|
|
13
|
+
* That defeats the reason injectable dates exist. The engine never reads the
|
|
14
|
+
* wall clock (D-012, PRD §5) so that its answers are reproducible from their
|
|
15
|
+
* inputs — and a date silently replaced by a different date is not the input
|
|
16
|
+
* anybody gave. Worse for `log-entry`, whose `--date` is stamped into fragment
|
|
17
|
+
* filenames and status transitions: a rolled-forward date lands in a permanent
|
|
18
|
+
* audit trail.
|
|
19
|
+
*
|
|
20
|
+
* One definition. The strict one.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** The shape: exactly four digits, two, two. Nothing else, no time, no zone. */
|
|
24
|
+
export const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* True when `value` is a YYYY-MM-DD string naming a day that actually exists.
|
|
28
|
+
*
|
|
29
|
+
* The round-trip is the whole check: `new Date('2026-02-30T00:00:00Z')` is a
|
|
30
|
+
* valid Date object (March 2nd), so `Number.isNaN` alone never catches it.
|
|
31
|
+
* Formatting it back and comparing to the input does.
|
|
32
|
+
*
|
|
33
|
+
* @param {unknown} value
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export function isCalendarDate(value) {
|
|
37
|
+
if (typeof value !== 'string' || !ISO_DATE.test(value)) return false;
|
|
38
|
+
const roundTrip = new Date(`${value}T00:00:00Z`);
|
|
39
|
+
if (Number.isNaN(roundTrip.getTime())) return false;
|
|
40
|
+
return roundTrip.toISOString().slice(0, 10) === value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whole days from ISO date `from` to ISO date `to`.
|
|
45
|
+
*
|
|
46
|
+
* Both must already be calendar dates — callers validate at their edge, where
|
|
47
|
+
* they can say which flag was wrong. UTC midnight throughout, so no timezone
|
|
48
|
+
* and no daylight-saving hour can move the answer.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} from
|
|
51
|
+
* @param {string} to
|
|
52
|
+
* @returns {number}
|
|
53
|
+
*/
|
|
54
|
+
export const daysBetween = (from, to) =>
|
|
55
|
+
Math.floor((Date.parse(`${to}T00:00:00Z`) - Date.parse(`${from}T00:00:00Z`)) / 86_400_000);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the Kit lives under a repo root — the one authority (UCS-934).
|
|
3
|
+
*
|
|
4
|
+
* Every repo-root-taking surface asks this module two questions, and there is
|
|
5
|
+
* exactly one answer to each: which directory holds the stores, and which
|
|
6
|
+
* root-relative paths are KIT ZONE (kit-vendored data the reverse audit must
|
|
7
|
+
* never propose concepts for — the map is never told to map itself).
|
|
8
|
+
*
|
|
9
|
+
* This used to be answered twice, with opposite tie-breaks: the loader
|
|
10
|
+
* preferred the nested seeded dir, the reverse audit preferred the repo root.
|
|
11
|
+
* A repo carrying both read as two different Stores depending on which surface
|
|
12
|
+
* asked, which voids the single-health-model guarantee — the audit could
|
|
13
|
+
* propose Concepts against one Store while preflight verdicted another.
|
|
14
|
+
*
|
|
15
|
+
* THERE IS NO TIE-BREAK, because the answer is not knowable. A repo carrying
|
|
16
|
+
* BOTH a seeded kit dir and root-level stores is ambiguous in a way no rule
|
|
17
|
+
* decides correctly:
|
|
18
|
+
*
|
|
19
|
+
* - a client with a product `ontology/` who seeds a kit wants the NESTED one
|
|
20
|
+
* - a client who moved their stores to the root, leaving a stale seeded dir
|
|
21
|
+
* behind, wants the ROOT one
|
|
22
|
+
*
|
|
23
|
+
* Both repos look identical from here. Picking either silently reads one Store
|
|
24
|
+
* and ignores the other — a confident wrong answer, which is the failure class
|
|
25
|
+
* this engine exists to prevent. So an ambiguous layout REFUSES: every surface
|
|
26
|
+
* fails identically (exit 2) and names both candidates, rather than four
|
|
27
|
+
* surfaces agreeing on one Store while the audit quietly reads the other.
|
|
28
|
+
*
|
|
29
|
+
* Unambiguous layouts resolve without ceremony: a seeded dir alone is the Kit
|
|
30
|
+
* (the D-016 default `init` creates, and where the generated platform wrappers
|
|
31
|
+
* point every agent); otherwise the repo root is, which is how the kit's own
|
|
32
|
+
* repo eats its own cooking and how every engine test fixture is laid out.
|
|
33
|
+
* Stores absent entirely is not an error: the loader reports missing-store
|
|
34
|
+
* warnings and the audit proposes every anchor.
|
|
35
|
+
*/
|
|
36
|
+
import { statSync } from 'node:fs';
|
|
37
|
+
import { join } from 'node:path';
|
|
38
|
+
import { EngineRefusal } from './engine-refusal.js';
|
|
39
|
+
|
|
40
|
+
/** The §9.1/D-016 seeded kit directory name. Renames are a later seam. */
|
|
41
|
+
export const KIT_DIR_DEFAULT = 'unknown-knowledge';
|
|
42
|
+
|
|
43
|
+
/** The human-confirmed survey boundary (§6), written at the kit root. */
|
|
44
|
+
export const SCOPE_FILE = 'survey-scope.yaml';
|
|
45
|
+
|
|
46
|
+
/** Client-zone suppression file (KK-27/D-013) — kit zone, at the kit root. */
|
|
47
|
+
export const SUPPRESSIONS_FILE = 'suppressions.yaml';
|
|
48
|
+
|
|
49
|
+
/** The three governed stores plus the fragment logs (§3, D-010). */
|
|
50
|
+
const STORE_DIRS = ['ontology', 'knowledge', 'decisions', 'logs'];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Kit zone when the stores live at the scan root itself. The seeded dir name
|
|
54
|
+
* stays listed as belt-and-braces: a seeded DIRECTORY here would now refuse as
|
|
55
|
+
* ambiguous before the zone is ever consulted, but a stray file by that name
|
|
56
|
+
* is still kit-shaped and never product surface to propose concepts for.
|
|
57
|
+
*/
|
|
58
|
+
const KIT_ZONE_AT_ROOT = [...STORE_DIRS, SCOPE_FILE, SUPPRESSIONS_FILE, KIT_DIR_DEFAULT];
|
|
59
|
+
|
|
60
|
+
const isDir = (path) => !!statSync(path, { throwIfNoEntry: false })?.isDirectory();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The repo root is itself a store root when it carries the artifact-owned or
|
|
64
|
+
* world-owned stores. `decisions/` alone does not count: the kit's own repo
|
|
65
|
+
* keeps a decisions store at its root without being a seeded kit.
|
|
66
|
+
*/
|
|
67
|
+
const looksLikeStoreRoot = (root) => isDir(join(root, 'ontology')) || isDir(join(root, 'knowledge'));
|
|
68
|
+
|
|
69
|
+
/** A repo whose Kit cannot be identified — never resolved by guessing. */
|
|
70
|
+
export class AmbiguousKitLayout extends EngineRefusal {
|
|
71
|
+
// Without this, `err.name` reads "Error" and a logged refusal hides which
|
|
72
|
+
// failure it was — the exit-2 message should identify itself.
|
|
73
|
+
name = 'AmbiguousKitLayout';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Locate the Kit under a repo root.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} root the repo root (`--root`), never the store dir
|
|
80
|
+
* @returns {{ kitRoot: string, kitPrefixes: string[] }} the directory holding
|
|
81
|
+
* the stores, and the root-relative paths that are kit zone
|
|
82
|
+
* @throws {AmbiguousKitLayout} when a seeded kit dir and root-level stores
|
|
83
|
+
* both exist, so no surface can know which Store is authoritative
|
|
84
|
+
*/
|
|
85
|
+
export function locateKit(root) {
|
|
86
|
+
const nested = join(root, KIT_DIR_DEFAULT);
|
|
87
|
+
if (isDir(nested)) {
|
|
88
|
+
if (looksLikeStoreRoot(root)) {
|
|
89
|
+
throw new AmbiguousKitLayout(
|
|
90
|
+
`two candidate kit roots under ${JSON.stringify(root)}: the seeded ${KIT_DIR_DEFAULT}/ and stores at the root itself. `
|
|
91
|
+
+ 'Which one is authoritative is not knowable from here, and guessing would let the reverse audit read a different '
|
|
92
|
+
+ 'store than the validators (PRD §4, single health model). Point --root at the intended kit root, or remove the stale one.',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return { kitRoot: nested, kitPrefixes: [KIT_DIR_DEFAULT] };
|
|
96
|
+
}
|
|
97
|
+
return { kitRoot: root, kitPrefixes: KIT_ZONE_AT_ROOT };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The store root alone, for surfaces that never scan for product anchors. */
|
|
101
|
+
export const locateKitRoot = (root) => locateKit(root).kitRoot;
|