@davesheffer/hunch 1.32.8 → 1.33.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/README.md +9 -3
- package/dist/cli/index.js +2 -0
- package/dist/cli/serve.js +28 -2
- package/dist/cli/state.d.ts +3 -0
- package/dist/cli/state.js +150 -0
- package/dist/client/state.d.ts +82 -14
- package/dist/client/state.js +16 -2
- package/dist/client/stateProof.d.ts +4 -0
- package/dist/client/stateProof.js +17 -0
- package/dist/constitution/behaviorEvaluator.js +1 -1
- package/dist/constitution/schema.d.ts +2 -2
- package/dist/core/automaticReviewMemory.d.ts +5 -0
- package/dist/core/conventionDelivery.d.ts +8 -0
- package/dist/core/conventionDelivery.js +52 -0
- package/dist/core/fieldProvenance.d.ts +8 -0
- package/dist/core/fieldProvenance.js +72 -0
- package/dist/core/recordVisibility.d.ts +9 -0
- package/dist/core/recordVisibility.js +25 -0
- package/dist/core/stateCanonical.d.ts +3 -0
- package/dist/core/stateCanonical.js +34 -0
- package/dist/core/stateContract.d.ts +122 -7
- package/dist/core/stateContract.js +26 -31
- package/dist/core/stateDelivery.d.ts +3 -3
- package/dist/core/stateDelivery.js +10 -1
- package/dist/core/stateHttp.d.ts +280 -0
- package/dist/core/stateHttp.js +17 -0
- package/dist/core/stateProof.d.ts +13 -0
- package/dist/core/stateProof.js +34 -0
- package/dist/core/stateRecords.d.ts +127 -0
- package/dist/core/stateRecords.js +48 -0
- package/dist/core/types.d.ts +146 -4
- package/dist/core/types.js +8 -2
- package/dist/extractors/git.js +3 -10
- package/dist/mcp/server.js +10 -4
- package/dist/serve/app.d.ts +2 -0
- package/dist/serve/app.js +71 -30
- package/dist/serve/config.d.ts +16 -0
- package/dist/serve/config.js +27 -7
- package/dist/serve/operator.d.ts +4 -0
- package/dist/serve/operator.js +223 -0
- package/dist/serve/stateProof.d.ts +15 -0
- package/dist/serve/stateProof.js +105 -0
- package/dist/store/changeLedger.d.ts +6 -0
- package/dist/store/hunchStore.d.ts +4 -2
- package/dist/store/hunchStore.js +18 -19
- package/dist/store/stateAccess.d.ts +13 -0
- package/dist/store/stateAccess.js +85 -0
- package/dist/store/stateBinding.d.ts +13 -18
- package/dist/store/stateBinding.js +161 -52
- package/dist/store/stateCapture.js +10 -2
- package/dist/store/stateError.d.ts +12 -0
- package/dist/store/stateError.js +12 -0
- package/dist/store/statePartition.d.ts +9 -0
- package/dist/store/statePartition.js +30 -0
- package/package.json +5 -1
- package/server.json +2 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { stateHash } from "./stateCanonical.js";
|
|
2
|
+
/** Resolve exactly one scalar JSON field or one Unicode-code-point text range.
|
|
3
|
+
* No normalization, inherited properties, array aliases or substring guessing. */
|
|
4
|
+
export function fieldCitationValue(content, selector) {
|
|
5
|
+
if (selector.kind === "text") {
|
|
6
|
+
const points = Array.from(content);
|
|
7
|
+
if (!Number.isInteger(selector.start) || !Number.isInteger(selector.end) || selector.start < 0 || selector.end <= selector.start || selector.end > points.length)
|
|
8
|
+
throw new Error("text citation range must be nonempty and within the content's Unicode code points");
|
|
9
|
+
return points.slice(selector.start, selector.end).join("");
|
|
10
|
+
}
|
|
11
|
+
if (!/^(?:\/(?:[^~/]|~[01])*)*$/.test(selector.path))
|
|
12
|
+
throw new Error("citation path must be an escaped JSON Pointer");
|
|
13
|
+
let value;
|
|
14
|
+
try {
|
|
15
|
+
value = JSON.parse(content);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error("JSON field citations require JSON content");
|
|
19
|
+
}
|
|
20
|
+
for (const encoded of selector.path === "" ? [] : selector.path.slice(1).split("/")) {
|
|
21
|
+
const key = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
22
|
+
if (value === null || typeof value !== "object" || (Array.isArray(value) && !/^(0|[1-9][0-9]*)$/.test(key)) || !Object.hasOwn(value, key))
|
|
23
|
+
throw new Error(`citation path ${selector.path} does not name an existing field`);
|
|
24
|
+
value = value[key];
|
|
25
|
+
}
|
|
26
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || (typeof value === "number" && Number.isFinite(value)))
|
|
27
|
+
return value;
|
|
28
|
+
throw new Error("JSON field citations must name a scalar value, not an object or array");
|
|
29
|
+
}
|
|
30
|
+
/** This is structural traceability, not a check that a source supports a claim. */
|
|
31
|
+
export function assertFieldProvenance(record) {
|
|
32
|
+
if (!record.field_provenance)
|
|
33
|
+
return;
|
|
34
|
+
const dependencies = new Set(record.dependencies.map(stateHash));
|
|
35
|
+
const selectors = new Set();
|
|
36
|
+
for (const [index, citation] of record.field_provenance.entries()) {
|
|
37
|
+
try {
|
|
38
|
+
const key = stateHash(citation.selector);
|
|
39
|
+
if (selectors.has(key))
|
|
40
|
+
throw new Error("duplicate citation selector; combine its dependency hashes in one entry");
|
|
41
|
+
selectors.add(key);
|
|
42
|
+
const value = fieldCitationValue(record.content, citation.selector);
|
|
43
|
+
if (stateHash(value) !== citation.value_hash)
|
|
44
|
+
throw new Error("citation value_hash does not match the selected content; rebuild the citation after editing");
|
|
45
|
+
if (new Set(citation.dependency_hashes).size !== citation.dependency_hashes.length)
|
|
46
|
+
throw new Error("duplicate citation dependency hash");
|
|
47
|
+
if (!citation.dependency_hashes.length || citation.dependency_hashes.some(hash => !dependencies.has(hash)))
|
|
48
|
+
throw new Error("every citation dependency_hash must name an existing summary dependency");
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Error(`field_provenance[${index}]: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Bounded, plain-text source map for assistant clients; structured records remain complete. */
|
|
56
|
+
export function fieldCitationText(record) {
|
|
57
|
+
if (!record.field_provenance?.length)
|
|
58
|
+
return "";
|
|
59
|
+
const dependencies = new Map(record.dependencies.map(dep => [stateHash(dep), dep]));
|
|
60
|
+
const source = (dep) => !dep ? "unavailable source" : dep.kind === "external"
|
|
61
|
+
? `${dep.ref.system} ${dep.ref.object_type}:${dep.ref.object_key}` : dep.kind === "record"
|
|
62
|
+
? `record ${dep.id}${dep.scope ? ` in ${dep.scope.kind}/${dep.scope.id}` : ""}` : `schema ${dep.name}`;
|
|
63
|
+
const lines = record.field_provenance.slice(0, 8).map(citation => {
|
|
64
|
+
const selector = citation.selector;
|
|
65
|
+
const target = selector.kind === "text" ? `text ${selector.start}–${selector.end} (Unicode code points)` : `field ${selector.path || "(root)"}`;
|
|
66
|
+
const value = JSON.stringify(fieldCitationValue(record.content, selector));
|
|
67
|
+
const sources = citation.dependency_hashes.slice(0, 4).map(hash => source(dependencies.get(hash)).slice(0, 200)).join("; ");
|
|
68
|
+
return ` ${target}: ${value.slice(0, 200)}${value.length > 200 ? "…" : ""} ← ${sources}${citation.dependency_hashes.length > 4 ? "; more sources in structured record" : ""}`;
|
|
69
|
+
});
|
|
70
|
+
return `\n Writer-supplied field citations (traceability, not verified support or freshness):\n${lines.join("\n")}${record.field_provenance.length > 8 ? "\n More citations in structured record." : ""}`;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=fieldProvenance.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Inline policy stays bound to the record revision and its atomic JSON write. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const RecordVisibilitySchema: z.ZodObject<{
|
|
4
|
+
owner: z.ZodString;
|
|
5
|
+
readers: z.ZodArray<z.ZodString>;
|
|
6
|
+
writers: z.ZodArray<z.ZodString>;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export type RecordVisibility = z.infer<typeof RecordVisibilitySchema>;
|
|
9
|
+
export declare function visibilityAllows(record: unknown, id: string, mode?: 'read' | 'write'): boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Inline policy stays bound to the record revision and its atomic JSON write. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const principalId = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,199}$/);
|
|
4
|
+
export const RecordVisibilitySchema = z.object({
|
|
5
|
+
owner: principalId,
|
|
6
|
+
readers: z.array(principalId).max(256),
|
|
7
|
+
writers: z.array(principalId).max(256),
|
|
8
|
+
}).strict().superRefine((value, ctx) => {
|
|
9
|
+
if (new Set(value.readers).size !== value.readers.length || new Set(value.writers).size !== value.writers.length)
|
|
10
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'visibility lists must contain distinct principal IDs' });
|
|
11
|
+
if (value.writers.some(id => id !== value.owner && !value.readers.includes(id)))
|
|
12
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'every writer must also be a reader' });
|
|
13
|
+
});
|
|
14
|
+
export function visibilityAllows(record, id, mode = 'read') {
|
|
15
|
+
if (!record || typeof record !== 'object')
|
|
16
|
+
return false;
|
|
17
|
+
const raw = record.visibility;
|
|
18
|
+
if (raw === undefined)
|
|
19
|
+
return true;
|
|
20
|
+
const parsed = RecordVisibilitySchema.safeParse(raw);
|
|
21
|
+
if (!parsed.success)
|
|
22
|
+
return false;
|
|
23
|
+
return parsed.data.owner === id || (mode === 'read' ? parsed.data.readers : parsed.data.writers).includes(id);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=recordVisibility.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Shared canonical form for state identity, revisions and citation validation. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { compareCodeUnits } from "./canonicalOrder.js";
|
|
4
|
+
/** Keep this encoding stable: existing record identities and dependency hashes use it. */
|
|
5
|
+
export function canonicalize(value) {
|
|
6
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
7
|
+
return value;
|
|
8
|
+
if (typeof value === "number") {
|
|
9
|
+
if (!Number.isFinite(value))
|
|
10
|
+
throw new Error("canonical form rejects non-finite numbers");
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.map(canonicalize);
|
|
15
|
+
if (typeof value === "object") {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const key of Object.keys(value).sort(compareCodeUnits)) {
|
|
18
|
+
// The historical encoding assigns into an ordinary object. This key would
|
|
19
|
+
// invoke its prototype setter and disappear from JSON. Refuse ambiguous
|
|
20
|
+
// input rather than silently collide or change existing valid identities.
|
|
21
|
+
if (key === "__proto__")
|
|
22
|
+
throw new Error("canonical form rejects reserved key __proto__");
|
|
23
|
+
const v = value[key];
|
|
24
|
+
if (v !== undefined)
|
|
25
|
+
out[key] = canonicalize(v);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`canonical form rejects ${typeof value}`);
|
|
30
|
+
}
|
|
31
|
+
export function stateHash(value) {
|
|
32
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(canonicalize(value))).digest("hex")}`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=stateCanonical.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export { canonicalize, stateHash } from "./stateCanonical.js";
|
|
3
|
+
export { fieldCitationValue, assertFieldProvenance } from "./fieldProvenance.js";
|
|
2
4
|
import { type DeliveryEnvelope } from "./delivery.js";
|
|
3
|
-
import { type Scope, type ActionReceipt, type Commitment, type DerivedState } from "./stateRecords.js";
|
|
5
|
+
import { type Convention, type Scope, type ActionReceipt, type Commitment, type DerivedState } from "./stateRecords.js";
|
|
4
6
|
export * from "./stateRecords.js";
|
|
5
7
|
export declare const STATE_CONTRACT_VERSION: "nuryel.state/1";
|
|
6
8
|
export declare const STATE_READ_VERSION: "nuryel.state.read/1";
|
|
@@ -12,9 +14,21 @@ export declare const STATE_CAPTURE_BATCH_VERSION: "nuryel.state.capture-batch/1"
|
|
|
12
14
|
export declare const STATE_OBSERVATION_LINKS_VERSION: "nuryel.observation-links/1";
|
|
13
15
|
export declare const STATE_OBSERVATION_REVIEW_VERSION: "nuryel.observation-review/1";
|
|
14
16
|
export declare const STATE_OBSERVATION_PAGES_VERSION: "nuryel.observation-pages/1";
|
|
17
|
+
export declare const STATE_RECORD_VISIBILITY_VERSION: "nuryel.record-visibility/1";
|
|
18
|
+
export declare const PartitionDeclarationSchema: z.ZodObject<{
|
|
19
|
+
kind: z.ZodEnum<{
|
|
20
|
+
repository: "repository";
|
|
21
|
+
organization: "organization";
|
|
22
|
+
team: "team";
|
|
23
|
+
user: "user";
|
|
24
|
+
}>;
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
required_capabilities: z.ZodOptional<z.ZodArray<z.ZodLiteral<"nuryel.record-visibility/1">>>;
|
|
27
|
+
}, z.core.$strict>;
|
|
28
|
+
export declare const STATE_FIELD_PROVENANCE_VERSION: "nuryel.field-provenance/1";
|
|
15
29
|
/** Capabilities a server advertises; a client that needs one the server lacks gets a typed
|
|
16
30
|
* `unsupported`, never a compatible-looking degraded answer. */
|
|
17
|
-
export declare const STATE_CAPABILITIES: readonly ["nuryel.state.read/1", "nuryel.state.write/1", "nuryel.state.subscribe/1", "nuryel.state.records/1", "nuryel.state.capture/1", "nuryel.state.capture-batch/1", "nuryel.observation-links/1", "nuryel.observation-review/1", "nuryel.observation-pages/1", "nuryel.receipt/1", "nuryel.commitment/1", "nuryel.derived/1", "nuryel.entity/1", "nuryel.relationship/1"];
|
|
31
|
+
export declare const STATE_CAPABILITIES: readonly ["nuryel.state.read/1", "nuryel.state.write/1", "nuryel.state.subscribe/1", "nuryel.state.records/1", "nuryel.state.capture/1", "nuryel.state.capture-batch/1", "nuryel.observation-links/1", "nuryel.observation-review/1", "nuryel.observation-pages/1", "nuryel.field-provenance/1", "nuryel.record-visibility/1", "nuryel.convention/1", "nuryel.receipt/1", "nuryel.commitment/1", "nuryel.derived/1", "nuryel.entity/1", "nuryel.relationship/1"];
|
|
18
32
|
export type StateCapability = (typeof STATE_CAPABILITIES)[number];
|
|
19
33
|
/** Who is reading or writing. Grants are the scopes the principal may see; authorization is
|
|
20
34
|
* decided BEFORE retrieval against these, never after ranking. */
|
|
@@ -40,6 +54,11 @@ export type Principal = z.infer<typeof PrincipalSchema>;
|
|
|
40
54
|
/** One relevant assertion, never a whole conversation. Source text is transient input:
|
|
41
55
|
* only its exact supporting excerpt and a hashed external pointer may reach the store. */
|
|
42
56
|
export declare const CaptureRequestSchema: z.ZodObject<{
|
|
57
|
+
visibility: z.ZodOptional<z.ZodObject<{
|
|
58
|
+
owner: z.ZodString;
|
|
59
|
+
readers: z.ZodArray<z.ZodString>;
|
|
60
|
+
writers: z.ZodArray<z.ZodString>;
|
|
61
|
+
}, z.core.$strict>>;
|
|
43
62
|
schema: z.ZodLiteral<"nuryel.state.capture/1">;
|
|
44
63
|
principal: z.ZodObject<{
|
|
45
64
|
id: z.ZodString;
|
|
@@ -148,6 +167,11 @@ export declare const CaptureBatchRequestSchema: z.ZodObject<{
|
|
|
148
167
|
}>;
|
|
149
168
|
reason: z.ZodString;
|
|
150
169
|
}, z.core.$strict>;
|
|
170
|
+
visibility: z.ZodOptional<z.ZodObject<{
|
|
171
|
+
owner: z.ZodString;
|
|
172
|
+
readers: z.ZodArray<z.ZodString>;
|
|
173
|
+
writers: z.ZodArray<z.ZodString>;
|
|
174
|
+
}, z.core.$strict>>;
|
|
151
175
|
subject: z.ZodString;
|
|
152
176
|
statement: z.ZodString;
|
|
153
177
|
evidence: z.ZodArray<z.ZodObject<{
|
|
@@ -172,7 +196,7 @@ export declare function captureTransform(scope: Scope, subject: string, statemen
|
|
|
172
196
|
source: string;
|
|
173
197
|
excerpt: string;
|
|
174
198
|
}[]): string;
|
|
175
|
-
export declare const STATE_FACETS: readonly ["decisions", "constraints", "bugs", "findings", "receipts", "commitments", "derived", "entities", "relationships"];
|
|
199
|
+
export declare const STATE_FACETS: readonly ["decisions", "constraints", "bugs", "findings", "receipts", "commitments", "derived", "entities", "relationships", "conventions"];
|
|
176
200
|
export type StateFacet = (typeof STATE_FACETS)[number];
|
|
177
201
|
/** Union read: the partitions a principal wants in ONE answer. `scope` stays required (it is the
|
|
178
202
|
* primary partition; its envelope and receipt lead the response). An entry the principal is not
|
|
@@ -246,6 +270,7 @@ export declare const ReadRequestSchema: z.ZodObject<{
|
|
|
246
270
|
derived: "derived";
|
|
247
271
|
entities: "entities";
|
|
248
272
|
relationships: "relationships";
|
|
273
|
+
conventions: "conventions";
|
|
249
274
|
}>>>;
|
|
250
275
|
observed_page: z.ZodOptional<z.ZodObject<{
|
|
251
276
|
cursor: z.ZodOptional<z.ZodObject<{
|
|
@@ -266,6 +291,7 @@ declare const StateRefSchema: z.ZodObject<{
|
|
|
266
291
|
derived: "derived";
|
|
267
292
|
entities: "entities";
|
|
268
293
|
relationships: "relationships";
|
|
294
|
+
conventions: "conventions";
|
|
269
295
|
}>;
|
|
270
296
|
id: z.ZodString;
|
|
271
297
|
record_hash: z.ZodString;
|
|
@@ -295,6 +321,7 @@ export declare const StateOfRecordSchema: z.ZodObject<{
|
|
|
295
321
|
derived: "derived";
|
|
296
322
|
entities: "entities";
|
|
297
323
|
relationships: "relationships";
|
|
324
|
+
conventions: "conventions";
|
|
298
325
|
}>;
|
|
299
326
|
id: z.ZodString;
|
|
300
327
|
record_hash: z.ZodString;
|
|
@@ -319,6 +346,7 @@ export declare const StateOfRecordSchema: z.ZodObject<{
|
|
|
319
346
|
derived: "derived";
|
|
320
347
|
entities: "entities";
|
|
321
348
|
relationships: "relationships";
|
|
349
|
+
conventions: "conventions";
|
|
322
350
|
}>;
|
|
323
351
|
id: z.ZodString;
|
|
324
352
|
record_hash: z.ZodString;
|
|
@@ -343,6 +371,7 @@ export declare const StateOfRecordSchema: z.ZodObject<{
|
|
|
343
371
|
derived: "derived";
|
|
344
372
|
entities: "entities";
|
|
345
373
|
relationships: "relationships";
|
|
374
|
+
conventions: "conventions";
|
|
346
375
|
}>;
|
|
347
376
|
id: z.ZodString;
|
|
348
377
|
record_hash: z.ZodString;
|
|
@@ -367,6 +396,7 @@ export declare const StateOfRecordSchema: z.ZodObject<{
|
|
|
367
396
|
derived: "derived";
|
|
368
397
|
entities: "entities";
|
|
369
398
|
relationships: "relationships";
|
|
399
|
+
conventions: "conventions";
|
|
370
400
|
}>;
|
|
371
401
|
id: z.ZodString;
|
|
372
402
|
record_hash: z.ZodString;
|
|
@@ -422,7 +452,82 @@ export declare const StateOfRecordSchema: z.ZodObject<{
|
|
|
422
452
|
invalidated_by: z.ZodArray<z.ZodString>;
|
|
423
453
|
}, z.core.$strict>;
|
|
424
454
|
export type StateOfRecord = z.infer<typeof StateOfRecordSchema>;
|
|
455
|
+
export declare const ConventionDeliverySchema: z.ZodObject<{
|
|
456
|
+
advisory: z.ZodLiteral<true>;
|
|
457
|
+
items: z.ZodArray<z.ZodObject<{
|
|
458
|
+
ref: z.ZodObject<{
|
|
459
|
+
facet: z.ZodEnum<{
|
|
460
|
+
decisions: "decisions";
|
|
461
|
+
constraints: "constraints";
|
|
462
|
+
bugs: "bugs";
|
|
463
|
+
findings: "findings";
|
|
464
|
+
receipts: "receipts";
|
|
465
|
+
commitments: "commitments";
|
|
466
|
+
derived: "derived";
|
|
467
|
+
entities: "entities";
|
|
468
|
+
relationships: "relationships";
|
|
469
|
+
conventions: "conventions";
|
|
470
|
+
}>;
|
|
471
|
+
id: z.ZodString;
|
|
472
|
+
record_hash: z.ZodString;
|
|
473
|
+
scope: z.ZodObject<{
|
|
474
|
+
kind: z.ZodEnum<{
|
|
475
|
+
repository: "repository";
|
|
476
|
+
organization: "organization";
|
|
477
|
+
team: "team";
|
|
478
|
+
user: "user";
|
|
479
|
+
}>;
|
|
480
|
+
id: z.ZodString;
|
|
481
|
+
}, z.core.$strict>;
|
|
482
|
+
}, z.core.$strict>;
|
|
483
|
+
key: z.ZodString;
|
|
484
|
+
conflict: z.ZodBoolean;
|
|
485
|
+
currentness: z.ZodEnum<{
|
|
486
|
+
stale: "stale";
|
|
487
|
+
recorded: "recorded";
|
|
488
|
+
}>;
|
|
489
|
+
}, z.core.$strict>>;
|
|
490
|
+
truncated: z.ZodBoolean;
|
|
491
|
+
}, z.core.$strict>;
|
|
492
|
+
export type ConventionDelivery = z.infer<typeof ConventionDeliverySchema>;
|
|
425
493
|
export declare const ReadResponseSchema: z.ZodObject<{
|
|
494
|
+
conventions: z.ZodOptional<z.ZodObject<{
|
|
495
|
+
advisory: z.ZodLiteral<true>;
|
|
496
|
+
items: z.ZodArray<z.ZodObject<{
|
|
497
|
+
ref: z.ZodObject<{
|
|
498
|
+
facet: z.ZodEnum<{
|
|
499
|
+
decisions: "decisions";
|
|
500
|
+
constraints: "constraints";
|
|
501
|
+
bugs: "bugs";
|
|
502
|
+
findings: "findings";
|
|
503
|
+
receipts: "receipts";
|
|
504
|
+
commitments: "commitments";
|
|
505
|
+
derived: "derived";
|
|
506
|
+
entities: "entities";
|
|
507
|
+
relationships: "relationships";
|
|
508
|
+
conventions: "conventions";
|
|
509
|
+
}>;
|
|
510
|
+
id: z.ZodString;
|
|
511
|
+
record_hash: z.ZodString;
|
|
512
|
+
scope: z.ZodObject<{
|
|
513
|
+
kind: z.ZodEnum<{
|
|
514
|
+
repository: "repository";
|
|
515
|
+
organization: "organization";
|
|
516
|
+
team: "team";
|
|
517
|
+
user: "user";
|
|
518
|
+
}>;
|
|
519
|
+
id: z.ZodString;
|
|
520
|
+
}, z.core.$strict>;
|
|
521
|
+
}, z.core.$strict>;
|
|
522
|
+
key: z.ZodString;
|
|
523
|
+
conflict: z.ZodBoolean;
|
|
524
|
+
currentness: z.ZodEnum<{
|
|
525
|
+
stale: "stale";
|
|
526
|
+
recorded: "recorded";
|
|
527
|
+
}>;
|
|
528
|
+
}, z.core.$strict>>;
|
|
529
|
+
truncated: z.ZodBoolean;
|
|
530
|
+
}, z.core.$strict>>;
|
|
426
531
|
schema: z.ZodLiteral<"nuryel.state.read/1">;
|
|
427
532
|
receipt_id: z.ZodString;
|
|
428
533
|
scope: z.ZodObject<{
|
|
@@ -447,6 +552,7 @@ export declare const ReadResponseSchema: z.ZodObject<{
|
|
|
447
552
|
derived: "derived";
|
|
448
553
|
entities: "entities";
|
|
449
554
|
relationships: "relationships";
|
|
555
|
+
conventions: "conventions";
|
|
450
556
|
}>;
|
|
451
557
|
id: z.ZodString;
|
|
452
558
|
record_hash: z.ZodString;
|
|
@@ -471,6 +577,7 @@ export declare const ReadResponseSchema: z.ZodObject<{
|
|
|
471
577
|
derived: "derived";
|
|
472
578
|
entities: "entities";
|
|
473
579
|
relationships: "relationships";
|
|
580
|
+
conventions: "conventions";
|
|
474
581
|
}>;
|
|
475
582
|
id: z.ZodString;
|
|
476
583
|
record_hash: z.ZodString;
|
|
@@ -495,6 +602,7 @@ export declare const ReadResponseSchema: z.ZodObject<{
|
|
|
495
602
|
derived: "derived";
|
|
496
603
|
entities: "entities";
|
|
497
604
|
relationships: "relationships";
|
|
605
|
+
conventions: "conventions";
|
|
498
606
|
}>;
|
|
499
607
|
id: z.ZodString;
|
|
500
608
|
record_hash: z.ZodString;
|
|
@@ -519,6 +627,7 @@ export declare const ReadResponseSchema: z.ZodObject<{
|
|
|
519
627
|
derived: "derived";
|
|
520
628
|
entities: "entities";
|
|
521
629
|
relationships: "relationships";
|
|
630
|
+
conventions: "conventions";
|
|
522
631
|
}>;
|
|
523
632
|
id: z.ZodString;
|
|
524
633
|
record_hash: z.ZodString;
|
|
@@ -645,6 +754,7 @@ export declare const WriteRequestSchema: z.ZodObject<{
|
|
|
645
754
|
derived: "derived";
|
|
646
755
|
entities: "entities";
|
|
647
756
|
relationships: "relationships";
|
|
757
|
+
conventions: "conventions";
|
|
648
758
|
}>;
|
|
649
759
|
record: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
650
760
|
idempotency_key: z.ZodString;
|
|
@@ -792,10 +902,16 @@ export declare const SubscribeRequestSchema: z.ZodObject<{
|
|
|
792
902
|
derived: "derived";
|
|
793
903
|
entities: "entities";
|
|
794
904
|
relationships: "relationships";
|
|
905
|
+
conventions: "conventions";
|
|
795
906
|
}>>>;
|
|
796
907
|
}, z.core.$strict>;
|
|
797
908
|
export type SubscribeRequest = z.infer<typeof SubscribeRequestSchema>;
|
|
798
909
|
export declare const ChangeEventSchema: z.ZodObject<{
|
|
910
|
+
visibility: z.ZodOptional<z.ZodObject<{
|
|
911
|
+
owner: z.ZodString;
|
|
912
|
+
readers: z.ZodArray<z.ZodString>;
|
|
913
|
+
writers: z.ZodArray<z.ZodString>;
|
|
914
|
+
}, z.core.$strict>>;
|
|
799
915
|
schema: z.ZodLiteral<"nuryel.state.subscribe/1">;
|
|
800
916
|
seq: z.ZodNumber;
|
|
801
917
|
at: z.ZodString;
|
|
@@ -818,6 +934,7 @@ export declare const ChangeEventSchema: z.ZodObject<{
|
|
|
818
934
|
derived: "derived";
|
|
819
935
|
entities: "entities";
|
|
820
936
|
relationships: "relationships";
|
|
937
|
+
conventions: "conventions";
|
|
821
938
|
}>;
|
|
822
939
|
record_id: z.ZodString;
|
|
823
940
|
record_hash: z.ZodString;
|
|
@@ -907,6 +1024,7 @@ export declare const RecordsResponseSchema: z.ZodObject<{
|
|
|
907
1024
|
derived: "derived";
|
|
908
1025
|
entities: "entities";
|
|
909
1026
|
relationships: "relationships";
|
|
1027
|
+
conventions: "conventions";
|
|
910
1028
|
}>>;
|
|
911
1029
|
missing: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
912
1030
|
denied: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -920,16 +1038,13 @@ export declare function negotiate(offered: readonly string[], required?: readonl
|
|
|
920
1038
|
supported: string[];
|
|
921
1039
|
unsupported: string[];
|
|
922
1040
|
};
|
|
923
|
-
/** Canonical JSON: keys sorted by code unit at every level, `undefined` dropped, non-finite
|
|
924
|
-
* numbers rejected. Two records with the same facts hash the same regardless of who wrote them. */
|
|
925
|
-
export declare function canonicalize(value: unknown): unknown;
|
|
926
|
-
export declare function stateHash(value: unknown): string;
|
|
927
1041
|
/** Identity = what makes two receipts the same action: who did what to which object, with which
|
|
928
1042
|
* request. Re-sending the same action replays the same receipt instead of minting a second one. */
|
|
929
1043
|
export declare function actionReceiptId(r: Pick<ActionReceipt, "scope" | "actor" | "action_kind" | "target" | "request_fingerprint"> & {
|
|
930
1044
|
idempotency_key?: string;
|
|
931
1045
|
}): string;
|
|
932
1046
|
export declare function commitmentId(c: Pick<Commitment, "scope" | "subject" | "title" | "owner" | "due">): string;
|
|
1047
|
+
export declare function conventionId(c: Pick<Convention, "scope" | "key" | "value" | "sources">): string;
|
|
933
1048
|
export declare function derivedId(d: Pick<DerivedState, "scope" | "subject" | "transform_version" | "dependencies">): string;
|
|
934
1049
|
export declare const STATE_INVARIANTS: readonly [{
|
|
935
1050
|
readonly id: "authorization-before-retrieval";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RecordVisibilitySchema } from "./recordVisibility.js";
|
|
1
2
|
/**
|
|
2
3
|
* nuryel.state/1 — the ONE contract every orchestrator and agent speaks to the state layer.
|
|
3
4
|
*
|
|
@@ -27,9 +28,13 @@
|
|
|
27
28
|
import { createHash } from "node:crypto";
|
|
28
29
|
import { z } from "zod";
|
|
29
30
|
import { compareCodeUnits } from "./canonicalOrder.js";
|
|
31
|
+
import { canonicalize, stateHash } from "./stateCanonical.js";
|
|
32
|
+
import { assertFieldProvenance } from "./fieldProvenance.js";
|
|
33
|
+
export { canonicalize, stateHash } from "./stateCanonical.js";
|
|
34
|
+
export { fieldCitationValue, assertFieldProvenance } from "./fieldProvenance.js";
|
|
30
35
|
import { DELIVERY_PROFILES } from "./delivery.js";
|
|
31
36
|
import { isHumanConfirmed as sourceIsHumanConfirmed } from "./strictgate.js";
|
|
32
|
-
import { ScopeSchema, scopePath, externalKey, DependencyRefSchema, ExternalRefSchema, RECEIPT_SCHEMA_VERSION, COMMITMENT_SCHEMA_VERSION, DERIVED_SCHEMA_VERSION, ENTITY_SCHEMA_VERSION, RELATIONSHIP_SCHEMA_VERSION, } from "./stateRecords.js";
|
|
37
|
+
import { ScopeSchema, scopePath, externalKey, DependencyRefSchema, ExternalRefSchema, CONVENTION_SCHEMA_VERSION, RECEIPT_SCHEMA_VERSION, COMMITMENT_SCHEMA_VERSION, DERIVED_SCHEMA_VERSION, ENTITY_SCHEMA_VERSION, RELATIONSHIP_SCHEMA_VERSION, } from "./stateRecords.js";
|
|
33
38
|
export * from "./stateRecords.js";
|
|
34
39
|
export const STATE_CONTRACT_VERSION = "nuryel.state/1";
|
|
35
40
|
export const STATE_READ_VERSION = "nuryel.state.read/1";
|
|
@@ -41,11 +46,14 @@ export const STATE_CAPTURE_BATCH_VERSION = "nuryel.state.capture-batch/1";
|
|
|
41
46
|
export const STATE_OBSERVATION_LINKS_VERSION = "nuryel.observation-links/1";
|
|
42
47
|
export const STATE_OBSERVATION_REVIEW_VERSION = "nuryel.observation-review/1";
|
|
43
48
|
export const STATE_OBSERVATION_PAGES_VERSION = "nuryel.observation-pages/1";
|
|
49
|
+
export const STATE_RECORD_VISIBILITY_VERSION = "nuryel.record-visibility/1";
|
|
50
|
+
export const PartitionDeclarationSchema = ScopeSchema.extend({ required_capabilities: z.array(z.literal(STATE_RECORD_VISIBILITY_VERSION)).min(1).max(1).optional() }).strict();
|
|
51
|
+
export const STATE_FIELD_PROVENANCE_VERSION = "nuryel.field-provenance/1";
|
|
44
52
|
/** Capabilities a server advertises; a client that needs one the server lacks gets a typed
|
|
45
53
|
* `unsupported`, never a compatible-looking degraded answer. */
|
|
46
54
|
export const STATE_CAPABILITIES = [
|
|
47
|
-
STATE_READ_VERSION, STATE_WRITE_VERSION, STATE_SUBSCRIBE_VERSION, STATE_RECORDS_VERSION, STATE_CAPTURE_VERSION, STATE_CAPTURE_BATCH_VERSION, STATE_OBSERVATION_LINKS_VERSION, STATE_OBSERVATION_REVIEW_VERSION, STATE_OBSERVATION_PAGES_VERSION,
|
|
48
|
-
RECEIPT_SCHEMA_VERSION, COMMITMENT_SCHEMA_VERSION, DERIVED_SCHEMA_VERSION, ENTITY_SCHEMA_VERSION, RELATIONSHIP_SCHEMA_VERSION,
|
|
55
|
+
STATE_READ_VERSION, STATE_WRITE_VERSION, STATE_SUBSCRIBE_VERSION, STATE_RECORDS_VERSION, STATE_CAPTURE_VERSION, STATE_CAPTURE_BATCH_VERSION, STATE_OBSERVATION_LINKS_VERSION, STATE_OBSERVATION_REVIEW_VERSION, STATE_OBSERVATION_PAGES_VERSION, STATE_FIELD_PROVENANCE_VERSION, STATE_RECORD_VISIBILITY_VERSION,
|
|
56
|
+
CONVENTION_SCHEMA_VERSION, RECEIPT_SCHEMA_VERSION, COMMITMENT_SCHEMA_VERSION, DERIVED_SCHEMA_VERSION, ENTITY_SCHEMA_VERSION, RELATIONSHIP_SCHEMA_VERSION,
|
|
49
57
|
];
|
|
50
58
|
const SHA256 = /^sha256:[a-f0-9]{64}$/;
|
|
51
59
|
// Explicit classes, no `i` flag: the pattern must survive zod → JSON schema for MCP output validation.
|
|
@@ -63,6 +71,7 @@ export const PrincipalSchema = z.object({
|
|
|
63
71
|
/** One relevant assertion, never a whole conversation. Source text is transient input:
|
|
64
72
|
* only its exact supporting excerpt and a hashed external pointer may reach the store. */
|
|
65
73
|
export const CaptureRequestSchema = z.object({
|
|
74
|
+
visibility: RecordVisibilitySchema.optional(),
|
|
66
75
|
schema: z.literal(STATE_CAPTURE_VERSION),
|
|
67
76
|
principal: PrincipalSchema,
|
|
68
77
|
scope: ScopeSchema,
|
|
@@ -82,7 +91,7 @@ export const CaptureRequestSchema = z.object({
|
|
|
82
91
|
export const CaptureBatchRequestSchema = z.object({
|
|
83
92
|
schema: z.literal(STATE_CAPTURE_BATCH_VERSION), principal: PrincipalSchema, scope: ScopeSchema,
|
|
84
93
|
sources: z.array(CaptureRequestSchema.shape.evidence.element.omit({ excerpt: true })).min(1).max(8),
|
|
85
|
-
observations: z.array(CaptureRequestSchema.pick({ subject: true, statement: true, relevance: true }).extend({
|
|
94
|
+
observations: z.array(CaptureRequestSchema.pick({ subject: true, statement: true, relevance: true, visibility: true }).extend({
|
|
86
95
|
evidence: z.array(z.object({ source: z.number().int().min(0).max(7), excerpt: z.string().trim().min(1).max(1200) }).strict()).min(1).max(8),
|
|
87
96
|
})).min(0).max(32),
|
|
88
97
|
reviews: z.array(z.object({
|
|
@@ -97,7 +106,7 @@ export function captureTransform(scope, subject, statement, evidence) {
|
|
|
97
106
|
const identities = [...new Set(evidence.map(e => stateHash({ source: e.source, excerpt: normalizeAssertion(e.excerpt) })))].sort();
|
|
98
107
|
return CAPTURE_TRANSFORM + stateHash({ scope, subject, statement: normalizeAssertion(statement), evidence: identities }).slice(7);
|
|
99
108
|
}
|
|
100
|
-
export const STATE_FACETS = ["decisions", "constraints", "bugs", "findings", "receipts", "commitments", "derived", "entities", "relationships"];
|
|
109
|
+
export const STATE_FACETS = ["decisions", "constraints", "bugs", "findings", "receipts", "commitments", "derived", "entities", "relationships", "conventions"];
|
|
101
110
|
// ---- verbs ------------------------------------------------------------------------------
|
|
102
111
|
/** Union read: the partitions a principal wants in ONE answer. `scope` stays required (it is the
|
|
103
112
|
* primary partition; its envelope and receipt lead the response). An entry the principal is not
|
|
@@ -140,7 +149,13 @@ export const StateOfRecordSchema = z.object({
|
|
|
140
149
|
depends_on: z.array(DependencyRefSchema).max(1024),
|
|
141
150
|
invalidated_by: z.array(z.string().max(512)).max(256),
|
|
142
151
|
}).strict();
|
|
152
|
+
export const ConventionDeliverySchema = z.object({
|
|
153
|
+
advisory: z.literal(true),
|
|
154
|
+
items: z.array(z.object({ ref: StateRefSchema, key: z.string(), conflict: z.boolean(), currentness: z.enum(["recorded", "stale"]) }).strict()).max(16),
|
|
155
|
+
truncated: z.boolean(),
|
|
156
|
+
}).strict();
|
|
143
157
|
export const ReadResponseSchema = z.object({
|
|
158
|
+
conventions: ConventionDeliverySchema.optional(),
|
|
144
159
|
schema: z.literal(STATE_READ_VERSION),
|
|
145
160
|
receipt_id: z.string().regex(/^hdr_[a-f0-9]{24}$/).describe("the delivery envelope's receipt"),
|
|
146
161
|
scope: ScopeSchema,
|
|
@@ -203,6 +218,7 @@ export const SubscribeRequestSchema = z.object({
|
|
|
203
218
|
facets: z.array(z.enum(STATE_FACETS)).max(STATE_FACETS.length).optional(),
|
|
204
219
|
}).strict();
|
|
205
220
|
export const ChangeEventSchema = z.object({
|
|
221
|
+
visibility: RecordVisibilitySchema.optional(),
|
|
206
222
|
schema: z.literal(STATE_SUBSCRIBE_VERSION),
|
|
207
223
|
seq: z.number().int().positive(),
|
|
208
224
|
at: z.string().regex(ISO),
|
|
@@ -251,32 +267,6 @@ export function negotiate(offered, required = STATE_CAPABILITIES) {
|
|
|
251
267
|
return { supported, unsupported };
|
|
252
268
|
}
|
|
253
269
|
// ---- canonical form, hashes, ids -----------------------------------------------------------
|
|
254
|
-
/** Canonical JSON: keys sorted by code unit at every level, `undefined` dropped, non-finite
|
|
255
|
-
* numbers rejected. Two records with the same facts hash the same regardless of who wrote them. */
|
|
256
|
-
export function canonicalize(value) {
|
|
257
|
-
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
258
|
-
return value;
|
|
259
|
-
if (typeof value === "number") {
|
|
260
|
-
if (!Number.isFinite(value))
|
|
261
|
-
throw new Error("canonical form rejects non-finite numbers");
|
|
262
|
-
return value;
|
|
263
|
-
}
|
|
264
|
-
if (Array.isArray(value))
|
|
265
|
-
return value.map(canonicalize);
|
|
266
|
-
if (typeof value === "object") {
|
|
267
|
-
const out = {};
|
|
268
|
-
for (const key of Object.keys(value).sort(compareCodeUnits)) {
|
|
269
|
-
const v = value[key];
|
|
270
|
-
if (v !== undefined)
|
|
271
|
-
out[key] = canonicalize(v);
|
|
272
|
-
}
|
|
273
|
-
return out;
|
|
274
|
-
}
|
|
275
|
-
throw new Error(`canonical form rejects ${typeof value}`);
|
|
276
|
-
}
|
|
277
|
-
export function stateHash(value) {
|
|
278
|
-
return `sha256:${createHash("sha256").update(JSON.stringify(canonicalize(value))).digest("hex")}`;
|
|
279
|
-
}
|
|
280
270
|
const idFrom = (prefix, seed) => `${prefix}_${createHash("sha256").update(JSON.stringify(canonicalize(seed))).digest("hex").slice(0, 24)}`;
|
|
281
271
|
/** Identity = what makes two receipts the same action: who did what to which object, with which
|
|
282
272
|
* request. Re-sending the same action replays the same receipt instead of minting a second one. */
|
|
@@ -286,6 +276,9 @@ export function actionReceiptId(r) {
|
|
|
286
276
|
export function commitmentId(c) {
|
|
287
277
|
return idFrom("ncm", { scope: c.scope, subject: c.subject, title: c.title.trim(), owner: c.owner, due: c.due });
|
|
288
278
|
}
|
|
279
|
+
export function conventionId(c) {
|
|
280
|
+
return idFrom("ncv", { scope: c.scope, key: c.key, value: c.value.trim(), sources: c.sources.map(stateHash).sort(compareCodeUnits) });
|
|
281
|
+
}
|
|
289
282
|
export function derivedId(d) {
|
|
290
283
|
// Capture's reserved transform includes assertion/evidence identity, independent of
|
|
291
284
|
// read time and unrelated source edits. Ordinary summary identity is unchanged.
|
|
@@ -322,6 +315,7 @@ export function assertReadWithinGrants(principal, response) {
|
|
|
322
315
|
if (!granted.has(grantKey(response.scope)))
|
|
323
316
|
throw new Error(`read response scope ${grantKey(response.scope)} is outside the principal's grants`);
|
|
324
317
|
const refs = response.state_of_record ? [...response.state_of_record.current, ...response.state_of_record.in_force, ...response.state_of_record.done, ...(response.state_of_record.observed ?? [])] : [];
|
|
318
|
+
refs.push(...(response.conventions?.items.map(item => item.ref) ?? []));
|
|
325
319
|
for (const ref of refs) {
|
|
326
320
|
if (!granted.has(grantKey(ref.scope)))
|
|
327
321
|
throw new Error(`state ref ${ref.id} in scope ${grantKey(ref.scope)} leaked outside the principal's grants`);
|
|
@@ -358,6 +352,7 @@ export function assertDerivedState(d) {
|
|
|
358
352
|
throw new Error("derived state without dependencies is not state");
|
|
359
353
|
if (stateHash(d.content) !== d.content_hash)
|
|
360
354
|
throw new Error("derived state content hash does not match its content");
|
|
355
|
+
assertFieldProvenance(d);
|
|
361
356
|
if (d.transform_version.startsWith(CAPTURE_TRANSFORM)) {
|
|
362
357
|
const content = z.object({
|
|
363
358
|
schema: z.literal("nuryel.observation-content/1"),
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
* Pure functions over records; no store or SQLite dependency, so the CLI, the MCP server and
|
|
16
16
|
* the store's own reindex/rank paths cannot drift from each other.
|
|
17
17
|
*/
|
|
18
|
-
import type { ActionReceipt, Commitment, DerivedState, ExternalEntity, StateRelationship } from "./stateRecords.js";
|
|
18
|
+
import type { Convention, ActionReceipt, Commitment, DerivedState, ExternalEntity, StateRelationship } from "./stateRecords.js";
|
|
19
19
|
import type { DeliverySupplement } from "./delivery.js";
|
|
20
|
-
export declare const STATE_KINDS: readonly ["receipts", "commitments", "derived", "entities", "relationships"];
|
|
20
|
+
export declare const STATE_KINDS: readonly ["receipts", "commitments", "derived", "entities", "relationships", "conventions"];
|
|
21
21
|
export type StateKind = (typeof STATE_KINDS)[number];
|
|
22
22
|
export declare function isStateKind(kind: string): kind is StateKind;
|
|
23
|
-
export type StateRecord = ActionReceipt | Commitment | DerivedState | ExternalEntity | StateRelationship;
|
|
23
|
+
export type StateRecord = Convention | ActionReceipt | Commitment | DerivedState | ExternalEntity | StateRelationship;
|
|
24
24
|
export interface StateLiveness {
|
|
25
25
|
/** The contract's own word for the record's standing: current, in_force, verified, done, superseded … */
|
|
26
26
|
label: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export const STATE_KINDS = ["receipts", "commitments", "derived", "entities", "relationships"];
|
|
1
|
+
export const STATE_KINDS = ["receipts", "commitments", "derived", "entities", "relationships", "conventions"];
|
|
2
2
|
const STATE_KIND_SET = new Set(STATE_KINDS);
|
|
3
3
|
export function isStateKind(kind) {
|
|
4
4
|
return STATE_KIND_SET.has(kind);
|
|
5
5
|
}
|
|
6
6
|
/** Singular facet label used in renders: `[commitment/in_force]`, `[derived/current]`. */
|
|
7
7
|
const FACET_LABEL = {
|
|
8
|
+
conventions: "convention",
|
|
8
9
|
receipts: "receipt",
|
|
9
10
|
commitments: "commitment",
|
|
10
11
|
derived: "derived",
|
|
@@ -16,6 +17,10 @@ const FACET_LABEL = {
|
|
|
16
17
|
* receipt succeeded/verified; entity active; a relationship is always current). */
|
|
17
18
|
export function stateLiveness(kind, record) {
|
|
18
19
|
switch (kind) {
|
|
20
|
+
case "conventions": {
|
|
21
|
+
const c = record;
|
|
22
|
+
return { label: c.valid_to != null ? 'superseded' : c.status, live: c.status === 'accepted' && c.valid_to === null && Date.parse(c.review_by) > Date.now() };
|
|
23
|
+
}
|
|
19
24
|
case "derived": {
|
|
20
25
|
const d = record;
|
|
21
26
|
if (d.valid_to != null)
|
|
@@ -45,6 +50,7 @@ export function stateLiveness(kind, record) {
|
|
|
45
50
|
* object (`event:10042`), the entity id, or the relationship's `from` endpoint. */
|
|
46
51
|
export function stateSubject(kind, record) {
|
|
47
52
|
switch (kind) {
|
|
53
|
+
case "conventions": return record.key;
|
|
48
54
|
case "derived": return record.subject;
|
|
49
55
|
case "commitments": return record.subject;
|
|
50
56
|
case "receipts": {
|
|
@@ -59,6 +65,7 @@ export function stateSubject(kind, record) {
|
|
|
59
65
|
* updated_at. Relationships carry no clock and sort last among equals. */
|
|
60
66
|
export function stateObservedAt(kind, record) {
|
|
61
67
|
switch (kind) {
|
|
68
|
+
case "conventions": return record.valid_from;
|
|
62
69
|
case "derived": return record.computed_at;
|
|
63
70
|
case "commitments": return record.valid_from;
|
|
64
71
|
case "receipts": {
|
|
@@ -77,6 +84,7 @@ export function stateSearchDoc(kind, record) {
|
|
|
77
84
|
const { label } = stateLiveness(kind, record);
|
|
78
85
|
const subject = stateSubject(kind, record);
|
|
79
86
|
switch (kind) {
|
|
87
|
+
case "conventions": return { title: subject, body: `${record.value} ${label} advisory convention` };
|
|
80
88
|
case "derived": {
|
|
81
89
|
const d = record;
|
|
82
90
|
return { title: subject, body: `${d.content} ${label} ${d.transform_version} ${d.computed_at.slice(0, 10)}` };
|
|
@@ -116,6 +124,7 @@ export function renderStateLine(kind, record) {
|
|
|
116
124
|
const { label } = stateLiveness(kind, record);
|
|
117
125
|
const head = `[${FACET_LABEL[kind]}/${label}] ${stateSubject(kind, record)} — `;
|
|
118
126
|
switch (kind) {
|
|
127
|
+
case "conventions": return `${head}${oneLine(record.value, 120)} (advisory)`;
|
|
119
128
|
case "derived":
|
|
120
129
|
return `${head}${oneLine(record.content, DERIVED_HEADLINE_CHARS)}`;
|
|
121
130
|
case "commitments": {
|