@polydeukes/core 0.4.0 → 0.6.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/dist/config.js CHANGED
@@ -1,31 +1,19 @@
1
1
  /**
2
- * Config schema v2 + `defineConfig()` validator — config as data (CONFIG-04).
2
+ * Config schema and the `defineConfig()` validator — config as data.
3
3
  *
4
- * This is the single settings surface the three areas share (covenant's `protectedPaths`,
5
- * ledger's `testCmd`, memory's ticket pattern all reference this shape). Since schema v2 the
6
- * input is pure JSON-representable data: `testCmd` is a `{scope}` template string, and
7
- * `defineConfig` is the runtime validator for parsed unknown data (the CONFIG-03 loader feeds
8
- * it values the compiler never saw). It stays a pure function — zero file I/O, zero runtime
9
- * dependencies (hand-rolled validation; the published JSON Schema is a sibling artifact the
10
- * source never reads).
4
+ * The single settings surface the areas share. The input is pure JSON-representable data,
5
+ * and `defineConfig` is the runtime validator for parsed unknown values the compiler never
6
+ * saw. It stays a pure function no file I/O and no runtime dependencies: validation is
7
+ * hand-rolled, and the published JSON Schema is a sibling artifact this source never reads.
11
8
  */
9
+ import { validateAlgebraDeclaration } from './algebra.js';
12
10
  import { isPlainObject } from './is-plain-object.js';
13
- /** Conventional default telemetry log path (PRD §4.3) — local-only observation data. */
11
+ import { ConfigValidationError, isNonEmptyString, isStringArray, rejectUnknownKeys, } from './validation.js';
12
+ export { ConfigValidationError } from './validation.js';
13
+ /** Conventional default telemetry log path — local-only observation data. */
14
14
  export const DEFAULT_TELEMETRY_LOG_PATH = '.polydeukes/roi.log';
15
- /**
16
- * `ConfigValidationError` raised when a config fails structural validation (PRD §4.3).
17
- *
18
- * The message names the offending field path so the developer sees exactly what is wrong.
19
- * This throw is a developer-time error (config authoring), a different axis from the
20
- * covenant runtime's fail-closed exit code — a bad config should fail loud and early.
21
- */
22
- export class ConfigValidationError extends Error {
23
- constructor(message) {
24
- super(message);
25
- this.name = 'ConfigValidationError';
26
- }
27
- }
28
- /** The exact key vocabulary of each object level — anything else is a typo, rejected loudly. */
15
+ /** Labels the assembly reserves for the judging chain's own registrations. */
16
+ const META_COVENANT_LABELS = ['self-mod', 'shell-mod', 'transcript-mod'];
29
17
  const TOP_LEVEL_KEYS = new Set([
30
18
  '$schema',
31
19
  'languages',
@@ -38,44 +26,18 @@ const TOP_LEVEL_KEYS = new Set([
38
26
  const PROFILE_KEYS = new Set(['productionGlob', 'testCmd']);
39
27
  const TELEMETRY_KEYS = new Set(['logPath']);
40
28
  const WITNESS_KEYS = new Set(['token', 'ttlMinutes']);
41
- const DISCIPLINE_KEYS = new Set([
42
- 'id',
43
- 'why',
44
- 'in',
45
- 'except',
46
- 'forbid',
47
- 'immutable',
48
- 'forbidCommand',
49
- 'when',
50
- 'requirePrecedent',
51
- ]);
52
- const PREDICATE_KEYS = ['forbid', 'immutable', 'forbidCommand', 'requirePrecedent'];
53
- /** Predicate families that `in`/`except` may scope — delta and context (COVENANT-13 §4.1). */
54
- const SCOPED_PREDICATE_KEYS = new Set(['forbid', 'requirePrecedent']);
55
- /** Throw on the first key outside the allowed vocabulary, naming the key and its location. */
56
- function rejectUnknownKeys(record, allowed, location) {
57
- for (const key of Object.keys(record)) {
58
- if (!allowed.has(key)) {
59
- throw new ConfigValidationError(`unknown key '${key}' in ${location}`);
60
- }
61
- }
62
- }
63
- /** True when the value is an array whose every element is a string. */
64
- function isStringArray(value) {
65
- return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
66
- }
29
+ const DISCIPLINE_KEYS = new Set(['id', 'why', 'enforce', 'declare']);
30
+ const DRAFT_KEYS = new Set(['id', 'why', 'draft']);
31
+ const ENFORCE_LEVELS = new Set(['block', 'advise']);
67
32
  /** True when the glob value is a present, non-empty string or a non-empty array of non-empty strings. */
68
33
  function isValidGlob(glob) {
69
34
  if (typeof glob === 'string') {
70
- return glob.length > 0;
35
+ return isNonEmptyString(glob);
71
36
  }
72
- if (Array.isArray(glob)) {
73
- return glob.length > 0 && glob.every((entry) => typeof entry === 'string' && entry.length > 0);
74
- }
75
- return false;
37
+ return Array.isArray(glob) && glob.length > 0 && glob.every(isNonEmptyString);
76
38
  }
77
39
  /**
78
- * Compile a `{scope}` template into the callable consumers use (PRD §4.2).
40
+ * Compile a `{scope}` template into the callable consumers use.
79
41
  *
80
42
  * Exactly the literal token `{scope}` is substituted, at every occurrence (`replaceAll`
81
43
  * semantics). Other braces (`${VAR}`, `{a,b}`, `awk '{print}'`) are the shell's own
@@ -86,49 +48,63 @@ function compileTestCmd(template) {
86
48
  // via GetSubstitution, breaking literal insertion for scopes containing `$`.
87
49
  return (scope) => template.replaceAll('{scope}', () => scope);
88
50
  }
89
- /** Throw unless the pattern string compiles with `new RegExp` — compilability only, never run. */
90
- function rejectUncompilableRegex(pattern, location) {
91
- try {
92
- new RegExp(pattern);
51
+ /** Validate a draft entry and return it as data. */
52
+ function validateDraft(entry, id, location) {
53
+ for (const key of Object.keys(entry)) {
54
+ if (!DRAFT_KEYS.has(key)) {
55
+ // Named as the draft rule, not as an unknown key: `declare` et al. are legal
56
+ // discipline keys, just not on a draft.
57
+ throw new ConfigValidationError(`${location} allows only id, why, draft on a draft entry (found '${key}')`);
58
+ }
59
+ }
60
+ if (entry.draft !== true) {
61
+ throw new ConfigValidationError(`${location} draft must be the literal true`);
62
+ }
63
+ if (typeof entry.why !== 'string' || entry.why.length === 0) {
64
+ throw new ConfigValidationError(`${location} why must be a non-empty string on a draft entry — the prose is its whole body`);
65
+ }
66
+ return { id, why: entry.why, draft: true };
67
+ }
68
+ /** Validate the head of a judged entry — the closed key set, `why`, and `enforce`. */
69
+ function validateEntryHead(entry, location) {
70
+ rejectUnknownKeys(entry, DISCIPLINE_KEYS, location);
71
+ if (entry.why !== undefined && typeof entry.why !== 'string') {
72
+ throw new ConfigValidationError(`${location} why must be a string`);
93
73
  }
94
- catch {
95
- throw new ConfigValidationError(`${location} must be a compilable regular expression`);
74
+ if (entry.enforce !== undefined &&
75
+ (typeof entry.enforce !== 'string' || !ENFORCE_LEVELS.has(entry.enforce))) {
76
+ throw new ConfigValidationError(`${location} enforce must be 'block' or 'advise'`);
96
77
  }
97
78
  }
98
79
  /**
99
- * Validate a context-family `requirePrecedent` value (COVENANT-13 §4.1).
80
+ * Validate a judged entry's declaration by delegating the block to the algebra validator.
100
81
  *
101
- * Evidence vocabulary is layered: the container (a flat object holding exactly one
102
- * evidence key) is the core's, and so is the `command` key — a shell command is the
103
- * agent-crossing surface, fully validated here. Every other key belongs to an adapter,
104
- * whose own validator judges the value; the core passes it through verbatim and never
105
- * inspects it (CONFIG-07 layering). An unrecognized evidence key fails closed at
106
- * assembly time, not here.
82
+ * The entry's `id` supplies the declaration's name, so the block carrying its own
83
+ * `discipline` is refused rather than silently overwritten. The delegated messages arrive
84
+ * with the entry's location in front of them, which is what places a failure among many
85
+ * entries.
107
86
  */
108
- function validateRequirePrecedent(evidence, location) {
109
- if (!isPlainObject(evidence)) {
110
- throw new ConfigValidationError(`${location} requirePrecedent must be an object`);
111
- }
112
- const keys = Object.keys(evidence);
113
- if (keys.length !== 1) {
114
- throw new ConfigValidationError(`${location} requirePrecedent must have exactly one evidence key`);
87
+ function validateDeclareEntry(entry, location) {
88
+ const block = entry.declare;
89
+ if (!isPlainObject(block)) {
90
+ throw new ConfigValidationError(`${location} declare must be an object`);
115
91
  }
116
- if (keys[0] === 'command') {
117
- const command = evidence.command;
118
- if (typeof command !== 'string' || command.length === 0) {
119
- throw new ConfigValidationError(`${location} requirePrecedent.command must be a non-empty string pattern`);
120
- }
121
- rejectUncompilableRegex(command, `${location} requirePrecedent.command`);
92
+ if ('discipline' in block) {
93
+ throw new ConfigValidationError(`${location} declare must not carry discipline — the entry id is the name`);
122
94
  }
95
+ validateAlgebraDeclaration({ discipline: entry.id, ...block }, `${location} declare`);
123
96
  }
124
97
  /**
125
- * Validate the `disciplines` array (COVENANT-10 §4.1). Throws {@link ConfigValidationError}
126
- * naming the offending entry/key; the validated data passes through verbatim.
98
+ * Validate the `disciplines` array and split judged entries from drafts. Throws
99
+ * {@link ConfigValidationError} naming the offending entry/key; the validated data passes
100
+ * through verbatim, in declaration order.
127
101
  */
128
102
  function validateDisciplines(disciplines) {
129
103
  if (!Array.isArray(disciplines)) {
130
104
  throw new ConfigValidationError('disciplines must be an array');
131
105
  }
106
+ const judged = [];
107
+ const drafts = [];
132
108
  const seenIds = new Set();
133
109
  disciplines.forEach((entry, index) => {
134
110
  if (!isPlainObject(entry)) {
@@ -141,93 +117,26 @@ function validateDisciplines(disciplines) {
141
117
  if (seenIds.has(entry.id)) {
142
118
  throw new ConfigValidationError(`${location} duplicates the id of an earlier entry`);
143
119
  }
144
- seenIds.add(entry.id);
145
- rejectUnknownKeys(entry, DISCIPLINE_KEYS, location);
146
- if (entry.why !== undefined && typeof entry.why !== 'string') {
147
- throw new ConfigValidationError(`${location} why must be a string`);
148
- }
149
- const predicates = PREDICATE_KEYS.filter((key) => entry[key] !== undefined);
150
- if (predicates.length !== 1) {
151
- throw new ConfigValidationError(`${location} must have exactly one predicate key ` +
152
- `(forbid | immutable | forbidCommand | requirePrecedent)`);
153
- }
154
- const predicate = predicates[0];
155
- if (!SCOPED_PREDICATE_KEYS.has(predicate) &&
156
- (entry.in !== undefined || entry.except !== undefined)) {
157
- throw new ConfigValidationError(`${location} allows in/except only on a forbid or requirePrecedent entry`);
158
- }
159
- // `when` is the context family's trigger; on any other family it would be dead data
160
- // implying a trigger that is never applied.
161
- if (entry.when !== undefined && predicate !== 'requirePrecedent') {
162
- throw new ConfigValidationError(`${location} allows when only on a requirePrecedent entry`);
163
- }
164
- if (entry.in !== undefined && !isValidGlob(entry.in)) {
165
- throw new ConfigValidationError(`${location} in must be a non-empty glob or glob array`);
120
+ // The three meta-covenant registrations share the telemetry label space with
121
+ // discipline ids; a colliding id would make gain aggregation and any label-keyed
122
+ // reader (pdks explain) ambiguous.
123
+ if (META_COVENANT_LABELS.includes(entry.id)) {
124
+ throw new ConfigValidationError(`${location} id collides with a meta-covenant label`);
166
125
  }
167
- if (entry.except !== undefined && !isValidGlob(entry.except)) {
168
- throw new ConfigValidationError(`${location} except must be a non-empty glob or glob array`);
169
- }
170
- if (predicate === 'forbid') {
171
- const forbid = entry.forbid;
172
- if (typeof forbid === 'string') {
173
- // An empty pattern matches at every position, so the entry would break every
174
- // in-scope change — rejected like every sibling pattern field.
175
- if (forbid.length === 0) {
176
- throw new ConfigValidationError(`${location} forbid must be a non-empty string pattern`);
177
- }
178
- rejectUncompilableRegex(forbid, `${location} forbid`);
179
- }
180
- else if (isPlainObject(forbid)) {
181
- // Only the { added } direction exists before COVENANT-12.
182
- const keys = Object.keys(forbid);
183
- if (keys.length !== 1 || keys[0] !== 'added' || typeof forbid.added !== 'string') {
184
- throw new ConfigValidationError(`${location} forbid object must have exactly one key 'added' with a string pattern`);
185
- }
186
- if (forbid.added.length === 0) {
187
- throw new ConfigValidationError(`${location} forbid.added must be a non-empty string pattern`);
188
- }
189
- rejectUncompilableRegex(forbid.added, `${location} forbid.added`);
190
- }
191
- else {
192
- throw new ConfigValidationError(`${location} forbid must be a string pattern or an { added } object`);
193
- }
194
- }
195
- else if (predicate === 'immutable') {
196
- if (!isValidGlob(entry.immutable)) {
197
- throw new ConfigValidationError(`${location} immutable must be a non-empty glob or glob array`);
198
- }
199
- }
200
- else if (predicate === 'forbidCommand') {
201
- if (typeof entry.forbidCommand !== 'string') {
202
- throw new ConfigValidationError(`${location} forbidCommand must be a string pattern`);
203
- }
204
- if (entry.forbidCommand.length === 0) {
205
- // An empty pattern matches every command line — one typo would block every
206
- // shell call the entry sees.
207
- throw new ConfigValidationError(`${location} forbidCommand must be a non-empty string pattern`);
208
- }
209
- rejectUncompilableRegex(entry.forbidCommand, `${location} forbidCommand`);
210
- }
211
- else {
212
- if (entry.when !== undefined) {
213
- if (typeof entry.when !== 'string') {
214
- throw new ConfigValidationError(`${location} when must be a string pattern`);
215
- }
216
- if (entry.when.length === 0) {
217
- // An empty pattern matches at every position, so the trigger would fire on any
218
- // file that merely grows — reject it like every sibling pattern field.
219
- throw new ConfigValidationError(`${location} when must be a non-empty string pattern`);
220
- }
221
- rejectUncompilableRegex(entry.when, `${location} when`);
222
- }
223
- validateRequirePrecedent(entry.requirePrecedent, location);
126
+ seenIds.add(entry.id);
127
+ // Selected by the marker's value, so an explicit `draft: undefined` is absence,
128
+ // like every other optional key in this validator.
129
+ if (entry.draft !== undefined) {
130
+ drafts.push(validateDraft(entry, entry.id, location));
131
+ return;
224
132
  }
133
+ validateEntryHead(entry, location);
134
+ validateDeclareEntry(entry, location);
135
+ judged.push(entry);
225
136
  });
226
- return disciplines;
137
+ return { judged, drafts };
227
138
  }
228
- /**
229
- * Validate the `languages` map and compile each profile's `{scope}` template (PRD §4.1).
230
- */
139
+ /** Validate the `languages` map and compile each profile's `{scope}` template. */
231
140
  function validateLanguages(languages) {
232
141
  if (!isPlainObject(languages) || Object.keys(languages).length === 0) {
233
142
  throw new ConfigValidationError('languages must be a non-empty object');
@@ -268,8 +177,8 @@ function validateProtectedPaths(protectedPaths) {
268
177
  return protectedPaths;
269
178
  }
270
179
  /**
271
- * Validate the `adapters` map (CONFIG-07 layering) — each namespace is a plain object whose
272
- * contents belong to that adapter's own validator, so they pass through verbatim.
180
+ * Validate the `adapters` map — each namespace is a plain object whose contents belong to
181
+ * that adapter's own validator, so they pass through verbatim.
273
182
  */
274
183
  function validateAdapters(adapters) {
275
184
  // Array first: the removed directory-list form deserves a migration hint, not a
@@ -302,7 +211,7 @@ function validateTelemetry(telemetry) {
302
211
  }
303
212
  return telemetry.logPath;
304
213
  }
305
- /** Validate the `witness` section (CONFIG-05) — both values are consumed at assembly time. */
214
+ /** Validate the `witness` section — both values are consumed at assembly time. */
306
215
  function validateWitness(witness) {
307
216
  if (!isPlainObject(witness)) {
308
217
  throw new ConfigValidationError('witness must be an object');
@@ -319,8 +228,7 @@ function validateWitness(witness) {
319
228
  }
320
229
  /**
321
230
  * Validate parsed unknown data as a {@link PolydeukesConfig} and return a
322
- * {@link ResolvedConfig} with defaults filled and templates compiled (PRD §4.3).
323
- * Pure — no file I/O.
231
+ * {@link ResolvedConfig} with defaults filled and templates compiled. Pure — no file I/O.
324
232
  *
325
233
  * Throws {@link ConfigValidationError} (naming the offending field path) when the top level
326
234
  * is not a plain object, any object level carries an unknown key, `languages` is
@@ -334,15 +242,17 @@ export function defineConfig(config) {
334
242
  throw new ConfigValidationError('config must be a plain object');
335
243
  }
336
244
  rejectUnknownKeys(config, TOP_LEVEL_KEYS, 'config');
337
- // `$schema` is an IDE schema reference (CONFIG-03): accepted, type-checked, and
338
- // ignored — it never appears in the resolution output.
245
+ // `$schema` is an IDE schema reference: accepted, type-checked, and ignored — it never
246
+ // appears in the resolution output.
339
247
  if (config.$schema !== undefined && typeof config.$schema !== 'string') {
340
248
  throw new ConfigValidationError('$schema must be a string');
341
249
  }
342
250
  const resolvedLanguages = validateLanguages(config.languages);
343
251
  const protectedPaths = config.protectedPaths !== undefined ? validateProtectedPaths(config.protectedPaths) : undefined;
344
252
  const adapters = config.adapters !== undefined ? validateAdapters(config.adapters) : undefined;
345
- const disciplines = config.disciplines !== undefined ? validateDisciplines(config.disciplines) : undefined;
253
+ const split = config.disciplines !== undefined ? validateDisciplines(config.disciplines) : undefined;
254
+ const disciplines = split?.judged;
255
+ const drafts = split !== undefined && split.drafts.length > 0 ? split.drafts : undefined;
346
256
  const logPath = config.telemetry !== undefined ? validateTelemetry(config.telemetry) : undefined;
347
257
  const witness = config.witness !== undefined ? validateWitness(config.witness) : undefined;
348
258
  return {
@@ -353,6 +263,7 @@ export function defineConfig(config) {
353
263
  logPath: logPath ?? DEFAULT_TELEMETRY_LOG_PATH,
354
264
  },
355
265
  ...(disciplines !== undefined && { disciplines }),
266
+ ...(drafts !== undefined && { drafts }),
356
267
  ...(witness !== undefined && { witness }),
357
268
  };
358
269
  }
@@ -1,16 +1,16 @@
1
1
  /**
2
- * exit-codes — the covenant protocol's exit-code vocabulary (CORE-01 PRD §4.1).
2
+ * exit-codes — the covenant protocol's exit-code vocabulary.
3
3
  *
4
- * The three codes are distinct and ordered by severity. The covenant *body* only
5
- * ever emits `0` (uphold) or `1` (break, non-blocking); translating a break into the
6
- * blocking `2` is the wrapper's job (COVENANT-01), never the core's. The sole place
7
- * the core itself reaches for `2` is the fail-closed parse path in the barrel.
4
+ * The three codes are distinct and ordered by severity. The covenant *body* only ever
5
+ * emits `0` (uphold) or `1` (break, non-blocking); translating a break into the blocking
6
+ * `2` is the wrapper's job, never the core's. The sole place the core itself reaches for
7
+ * `2` is the fail-closed parse path in `protocol.ts`.
8
8
  *
9
- * These live in their own module rather than the barrel because `fail-policy.ts` needs
10
- * them: importing them from the barrel, which re-exports fail-policy, is an
11
- * initialization cycle — the constants read as `undefined` depending on which module
12
- * the runtime evaluates first (CLEANUP-01 F1). The barrel re-exports them, so every
13
- * consumer outside core still reaches them at the same path.
9
+ * These live in their own leaf module because `fail-policy.ts` and `protocol.ts` both need
10
+ * them: importing them from the barrel, which re-exports both, is an initialization
11
+ * cycle — the constants read as `undefined` depending on which module the runtime evaluates
12
+ * first. The barrel re-exports them, so every consumer outside core still reaches them at
13
+ * the same path.
14
14
  */
15
15
  /** Promise upheld — no violation, the edit/push passes. */
16
16
  export declare const EXIT_UPHOLD = 0;
@@ -1,16 +1,16 @@
1
1
  /**
2
- * exit-codes — the covenant protocol's exit-code vocabulary (CORE-01 PRD §4.1).
2
+ * exit-codes — the covenant protocol's exit-code vocabulary.
3
3
  *
4
- * The three codes are distinct and ordered by severity. The covenant *body* only
5
- * ever emits `0` (uphold) or `1` (break, non-blocking); translating a break into the
6
- * blocking `2` is the wrapper's job (COVENANT-01), never the core's. The sole place
7
- * the core itself reaches for `2` is the fail-closed parse path in the barrel.
4
+ * The three codes are distinct and ordered by severity. The covenant *body* only ever
5
+ * emits `0` (uphold) or `1` (break, non-blocking); translating a break into the blocking
6
+ * `2` is the wrapper's job, never the core's. The sole place the core itself reaches for
7
+ * `2` is the fail-closed parse path in `protocol.ts`.
8
8
  *
9
- * These live in their own module rather than the barrel because `fail-policy.ts` needs
10
- * them: importing them from the barrel, which re-exports fail-policy, is an
11
- * initialization cycle — the constants read as `undefined` depending on which module
12
- * the runtime evaluates first (CLEANUP-01 F1). The barrel re-exports them, so every
13
- * consumer outside core still reaches them at the same path.
9
+ * These live in their own leaf module because `fail-policy.ts` and `protocol.ts` both need
10
+ * them: importing them from the barrel, which re-exports both, is an initialization
11
+ * cycle — the constants read as `undefined` depending on which module the runtime evaluates
12
+ * first. The barrel re-exports them, so every consumer outside core still reaches them at
13
+ * the same path.
14
14
  */
15
15
  /** Promise upheld — no violation, the edit/push passes. */
16
16
  export const EXIT_UPHOLD = 0;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * fail-policy — the failure-kind → fail-mode policy table (CORE-03).
2
+ * fail-policy — the failure-kind → fail-mode policy table.
3
3
  *
4
4
  * Pure and total: classifying a failure and mapping it to an exit code never
5
5
  * performs I/O and never throws. The single source of truth for "which failures
@@ -8,7 +8,7 @@
8
8
  /** How a failure resolves: 'open' passes the call through, 'closed' blocks it. */
9
9
  export type FailMode = 'open' | 'closed';
10
10
  /**
11
- * The registered failure kinds (PRD §4.1). Gate-integrity failures
11
+ * The registered failure kinds. Gate-integrity failures
12
12
  * (evidence-absence / input-parse / undecidable-structure) fail closed;
13
13
  * observability failures fail open so measurement loss never holds work hostage.
14
14
  */
@@ -16,14 +16,14 @@ export type FailureKind = 'evidence-absence' | 'input-parse' | 'undecidable-stru
16
16
  /**
17
17
  * Resolve a failure kind to its {@link FailMode} via the policy table.
18
18
  *
19
- * fail-closed default (PRD §5.2): any unregistered kind — including '' and
20
- * prototype-pollution keys — resolves to 'closed'. "Cannot classify" means
21
- * block. Pure and total (PRD §7): never throws, no I/O, no logging.
19
+ * fail-closed default: any unregistered kind — including '' and prototype-pollution
20
+ * keys — resolves to 'closed'. "Cannot classify" means block. Pure and total: never
21
+ * throws, no I/O, no logging.
22
22
  */
23
23
  export declare function resolveFailMode(kind: string): FailMode;
24
24
  /**
25
- * Map a {@link FailMode} to the covenant protocol's exit code (PRD §4.2):
25
+ * Map a {@link FailMode} to the covenant protocol's exit code:
26
26
  * 'open' → {@link EXIT_UPHOLD}, 'closed' → {@link EXIT_BREAK_BLOCKING}.
27
- * Reuses CORE-01's constants — no independent numeric literals here.
27
+ * Reuses the protocol's constants — no independent numeric literals here.
28
28
  */
29
29
  export declare function failModeToExitCode(mode: FailMode): 0 | 2;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * fail-policy — the failure-kind → fail-mode policy table (CORE-03).
2
+ * fail-policy — the failure-kind → fail-mode policy table.
3
3
  *
4
4
  * Pure and total: classifying a failure and mapping it to an exit code never
5
5
  * performs I/O and never throws. The single source of truth for "which failures
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { EXIT_BREAK_BLOCKING, EXIT_UPHOLD } from './exit-codes.js';
9
9
  /**
10
- * Policy table (PRD §4.1). Null-prototype so lookups can never reach
10
+ * Policy table. Null-prototype so lookups can never reach
11
11
  * Object.prototype members ('__proto__', 'toString', …) — those must resolve
12
12
  * to the fail-closed default, not to an inherited function.
13
13
  */
@@ -20,17 +20,17 @@ const FAIL_POLICY = Object.assign(Object.create(null), {
20
20
  /**
21
21
  * Resolve a failure kind to its {@link FailMode} via the policy table.
22
22
  *
23
- * fail-closed default (PRD §5.2): any unregistered kind — including '' and
24
- * prototype-pollution keys — resolves to 'closed'. "Cannot classify" means
25
- * block. Pure and total (PRD §7): never throws, no I/O, no logging.
23
+ * fail-closed default: any unregistered kind — including '' and prototype-pollution
24
+ * keys — resolves to 'closed'. "Cannot classify" means block. Pure and total: never
25
+ * throws, no I/O, no logging.
26
26
  */
27
27
  export function resolveFailMode(kind) {
28
28
  return FAIL_POLICY[kind] ?? 'closed';
29
29
  }
30
30
  /**
31
- * Map a {@link FailMode} to the covenant protocol's exit code (PRD §4.2):
31
+ * Map a {@link FailMode} to the covenant protocol's exit code:
32
32
  * 'open' → {@link EXIT_UPHOLD}, 'closed' → {@link EXIT_BREAK_BLOCKING}.
33
- * Reuses CORE-01's constants — no independent numeric literals here.
33
+ * Reuses the protocol's constants — no independent numeric literals here.
34
34
  */
35
35
  export function failModeToExitCode(mode) {
36
36
  return mode === 'open' ? EXIT_UPHOLD : EXIT_BREAK_BLOCKING;
package/dist/index.d.ts CHANGED
@@ -1,104 +1,18 @@
1
1
  /**
2
2
  * @polydeukes/core — the thin, domain- and agent-agnostic core.
3
3
  *
4
- * Pre-alpha. The covenant protocol (CORE-01) landed first, then the ROI telemetry
5
- * collector (CORE-02) and the config loader (CONFIG-01). Pure types and functions,
6
- * except telemetry's confined I/O functions (appendRecord / readRecords /
7
- * appendRecordFailOpen — the fail-open wrapper promoted by CORE-05).
4
+ * Alpha. Carries the covenant protocol, the ROI telemetry collector, and the config
5
+ * schema. Pure types and functions, except telemetry's confined I/O functions
6
+ * (appendRecord / readRecords / appendRecordFailOpen).
8
7
  * See https://github.com/huskyhoochu/polydeukes
9
8
  */
10
- export { ConfigValidationError, DEFAULT_TELEMETRY_LOG_PATH, type DisciplineEntry, type DisciplineForbid, defineConfig, type LanguageProfile, type PolydeukesConfig, type ResolvedConfig, type ResolvedLanguageProfile, } from './config.js';
11
- export { EXIT_BREAK_BLOCKING, EXIT_BREAK_NON_BLOCKING, EXIT_UPHOLD, } from './exit-codes.js';
12
- export { type FailMode, type FailureKind, failModeToExitCode, resolveFailMode, } from './fail-policy.js';
13
- export { isPlainObject } from './is-plain-object.js';
14
- export { normalizeProtectedPaths } from './protected-paths.js';
15
- export { aggregateGain, appendRecord, appendRecordFailOpen, formatRecordLine, type GainSummary, parseRecordLine, readRecords, runGain, type TelemetryEvent, type TelemetryRecord, } from './telemetry.js';
16
- export { type CanonicalTranscript, noopTranscript, type SubagentInvocation, type TranscriptToolCall, type TranscriptUserMessage, transcriptFromInput, } from './transcript.js';
17
- /**
18
- * `FileChange` one file's mutation evidence around the judged call (CORE-06 §4.1).
19
- *
20
- * Agent-neutral, discriminated by `kind`: a deletion is first-class evidence rather
21
- * than an unrepresentable case, and impossible states (a deletion with resulting
22
- * content, a creation with a baseline) cannot be written down. Adapters fill this from
23
- * their own sources (virtual apply, git blobs) — the core only transports it.
24
- * `delete.pre` is the readable text baseline when one exists — absent for a binary
25
- * blob, because a deletion needs no content to be judged.
26
- */
27
- export type FileChange = {
28
- kind: 'create';
29
- path: string;
30
- post: string;
31
- } | {
32
- kind: 'modify';
33
- path: string;
34
- pre: string;
35
- post: string;
36
- } | {
37
- kind: 'delete';
38
- path: string;
39
- pre?: string;
40
- };
41
- /**
42
- * `CovenantInput` — the agent-neutral input IR a covenant judges (PRD §4.2).
43
- *
44
- * Adapters up-translate their own agent payloads into this shape and pipe it as
45
- * stdin-JSON. The vocabulary carries no agent/tool literals; concrete tool or
46
- * subagent names are *values* an adapter fills in, never part of the core's type.
47
- * Evidence has exactly one home — the call element it belongs to (CORE-06 §4.1):
48
- * `fileChange` absent means "this call is unproven", and no sibling call's evidence
49
- * can stand in for it.
50
- */
51
- export type CovenantInput = {
52
- toolCalls: {
53
- name: string;
54
- args?: Record<string, unknown>;
55
- fileChange?: FileChange;
56
- }[];
57
- subagentSpawns: {
58
- kind: string;
59
- }[];
60
- userMessages: {
61
- text: string;
62
- }[];
63
- };
64
- /**
65
- * `CovenantVerdict` — the result a covenant body produces (PRD §4.3).
66
- *
67
- * Either the promise was upheld, or it was broken with a human-readable reason.
68
- * Maps to an exit code via {@link verdictToExitCode}.
69
- */
70
- export type CovenantVerdict = {
71
- upheld: true;
72
- } | {
73
- upheld: false;
74
- reason: string;
75
- };
76
- /**
77
- * Deserialize stdin-JSON into a {@link CovenantInput} (the protocol's reverse direction).
78
- *
79
- * fail-closed (PRD §5.2): this never throws. Any failure — unparseable JSON, an empty
80
- * payload, a parsed value that is not an object, or a missing required collection —
81
- * resolves to a blocking `{ ok: false, exitCode: 2 }`. "Cannot judge" means block,
82
- * so an unjudgeable input can never be mistaken for a valid one.
83
- */
84
- export declare function parseInput(stdinJson: string): {
85
- ok: true;
86
- value: CovenantInput;
87
- } | {
88
- ok: false;
89
- exitCode: 2;
90
- };
91
- /**
92
- * Flatten every call's evidence into one array in call order (CORE-06 §4.1).
93
- *
94
- * The one traversal for consumers that need no attribution (discipline scope, delta
95
- * judging): calls without evidence are skipped, never substituted for.
96
- */
97
- export declare function allFileChanges(input: CovenantInput): FileChange[];
98
- /**
99
- * Map a {@link CovenantVerdict} to an exit code (the protocol's forward direction).
100
- *
101
- * Responsibility boundary (PRD §4.1): the body emits `0` when upheld and `1` when
102
- * broken — never the blocking `2`. Translating `1` into `2` is the wrapper's policy.
103
- */
104
- export declare function verdictToExitCode(verdict: CovenantVerdict): 0 | 1;
9
+ export { type AlgebraDeclaration, BINARY_COMBINATOR_NAMES, type BinaryStep, type ExtractBlock, type ExtractStep, RELATION_NAMES, type RelateEntry, type RelationDecl, type RelationName, type ScopeBlock, SUPPLY_POLICIES, type SupplyBlock, type UnaryStep, validateAlgebraDeclaration, type Witness, type WitnessBlock, type Witnesses, } from './algebra.ts';
10
+ export { AXIS_NAMES, type Axis, type DerivableDeclaration, deriveShape, MECHANISM_NAMES, MECHANISM_SHAPES, type MechanismName, type MechanismShape, } from './catalogue.ts';
11
+ export { type AlgebraDeclarationBody, ConfigValidationError, DEFAULT_TELEMETRY_LOG_PATH, type DisciplineDraft, type DisciplineEntry, defineConfig, type EnforceLevel, type LanguageProfile, type PolydeukesConfig, type ResolvedConfig, type ResolvedLanguageProfile, } from './config.ts';
12
+ export { EXIT_BREAK_BLOCKING, EXIT_BREAK_NON_BLOCKING, EXIT_UPHOLD, } from './exit-codes.ts';
13
+ export { type FailMode, type FailureKind, failModeToExitCode, resolveFailMode, } from './fail-policy.ts';
14
+ export { isPlainObject } from './is-plain-object.ts';
15
+ export { normalizeProtectedPaths } from './protected-paths.ts';
16
+ export { allFileChanges, type ChannelReader, type CovenantInput, type CovenantVerdict, type DispatchOutcome, type FileChange, parseInput, type SourceReader, verdictToExitCode, } from './protocol.ts';
17
+ export { aggregateGain, appendRecord, appendRecordFailOpen, formatRecordLine, type GainSummary, parseRecordLine, readRecords, runGain, SKIP_REASONS, type SkipReason, type TelemetryEvent, type TelemetryRecord, } from './telemetry.ts';
18
+ export { type CanonicalTranscript, noopTranscript, type TranscriptToolCall, type TranscriptUserMessage, transcriptFromInput, } from './transcript.ts';