@archstone/compiler
Advanced tools
+79
-4
| import { LoadResult, PolicyDoc } from '@archstone/schema'; | ||
| type JsonType = "string" | "number" | "boolean" | "null" | "array" | "object"; | ||
| /** The closed set of `JsonType`, as a value — so a reader validating a recorded shape checks | ||
| * against the same list the writer produces, not a second copy of it. */ | ||
| declare const JSON_TYPES: readonly JsonType[]; | ||
| /** A recorded response shape: JSONPath-ish key path -> JSON type. Values are never present. */ | ||
| type ShapeMap = Record<string, JsonType>; | ||
| /** sha256:<hex> of a JSON value's shape. Two payloads with the same keys/types but | ||
| * different values fingerprint identically; a renamed or retyped key changes it. | ||
| * | ||
| * Computed from the raw entry list, NOT from `describeShape`, so its output is | ||
| * bit-for-bit what ADD-18 shipped — see `describeShape`'s note on duplicate paths. | ||
| * Every contract already committed in the wild depends on this not moving. */ | ||
| declare function fingerprintShape(value: unknown): string; | ||
| /** | ||
| * The same traversal as `fingerprintShape`, kept as a `path -> type` map (ADD-114 D-1) so a | ||
| * drift report can name paths and a human can read one in a binding. | ||
| * | ||
| * **Lossy in one pathological case, deliberately.** A JSON key containing a dot collides in | ||
| * this flattened path space — `{"a.b": 1, "a": {"b": 2}}` yields `$.a.b` twice, and a map keeps | ||
| * one. The pair list `fingerprintShape` hashes keeps both, which is why the two are computed | ||
| * from the same traversal but not from each other. The consequence is bounded and fail-safe: | ||
| * for such a payload `fingerprintShapeMap(describeShape(x)) !== fingerprintShape(x)`, so | ||
| * ADD-114 D-3's consistency check reports the recorded shape as stale and suppresses the diff, | ||
| * rather than naming fields from a shape that cannot represent this provider. Health is | ||
| * unaffected in every case — the fingerprint remains the sole authority (D-2). | ||
| */ | ||
| declare function describeShape(value: unknown): ShapeMap; | ||
| /** | ||
| * Re-derive a fingerprint from an already-recorded `ShapeMap`. | ||
| * | ||
| * Exists for ADD-114 D-3: `shape` and `fingerprint` are two records of one observation and can | ||
| * disagree if either is hand-edited, so `verify` re-derives one from the other before trusting | ||
| * a diff. For any payload without the duplicate-path collision above, | ||
| * `fingerprintShapeMap(describeShape(x)) === fingerprintShape(x)`. | ||
| */ | ||
| declare function fingerprintShapeMap(shape: ShapeMap): string; | ||
| type SemanticType = "location" | "date-range" | "party" | "preference-set" | "money" | "identifier" | "string" | "text" | "time-slot" | "quantity" | "enum" | "date" | "datetime"; | ||
@@ -77,2 +114,11 @@ /** The closed set of semantic types (mirrors cdl.schema.json). A field `type:` not in | ||
| fingerprint: string; | ||
| /** | ||
| * The recorded shape itself — `path -> type`, values never present (ADD-114 D-1). | ||
| * | ||
| * Optional, and NARRATIVE ONLY: `fingerprint` above remains the sole authority for a | ||
| * binding's health (ADD-114 D-2, preserving ADD-18 D-4). This exists so `verify` can name | ||
| * WHICH paths moved instead of only reporting that a hash did. A contract without it | ||
| * verifies exactly as it did before ADD-114. | ||
| */ | ||
| shape?: ShapeMap; | ||
| probeFixture: string; | ||
@@ -193,5 +239,34 @@ } | ||
| /** sha256:<hex> of a JSON value's shape. Two payloads with the same keys/types but | ||
| * different values fingerprint identically; a renamed or retyped key changes it. */ | ||
| declare function fingerprintShape(value: unknown): string; | ||
| interface ShapeAddition { | ||
| path: string; | ||
| type: JsonType; | ||
| } | ||
| interface ShapeRetype { | ||
| path: string; | ||
| from: JsonType; | ||
| to: JsonType; | ||
| } | ||
| /** What moved between a recorded shape and a live one. Every list is sorted by path, so | ||
| * two runs over the same pair of shapes produce identical reports. */ | ||
| interface ShapeDiff { | ||
| added: ShapeAddition[]; | ||
| removed: ShapeAddition[]; | ||
| retyped: ShapeRetype[]; | ||
| } | ||
| /** | ||
| * Compare a recorded response shape against a live one. | ||
| * | ||
| * A path present in both with the same type does not appear in the result — the diff carries | ||
| * only what changed, so an unchanged contract produces three empty lists rather than a full | ||
| * inventory the caller has to filter. | ||
| */ | ||
| declare function diffShape(recorded: ShapeMap, live: ShapeMap): ShapeDiff; | ||
| /** True when a diff has anything to report. */ | ||
| declare function hasShapeDrift(diff: ShapeDiff): boolean; | ||
| /** | ||
| * The operator-facing sentence for a diff — one spelling, so the human report and any future | ||
| * consumer never describe the same comparison differently (the ADD-19/`contractViolationMessage` | ||
| * precedent). | ||
| */ | ||
| declare function shapeDriftSummary(diff: ShapeDiff): string; | ||
@@ -220,2 +295,2 @@ type Severity = "error" | "warning"; | ||
| export { type Diagnostic, type IR, type IRConnector, type IRContract, type IRField, type IRFieldMapping, type IRPolicyRule, type IRResourceRegistry, type IRResponseMapping, type IRRestConnector, type IRTool, type IRType, LIFECYCLE_STATES, type Lifecycle, type PathParse, type Resolution, SEMANTIC_TYPES, type SemanticType, type Severity, compile, domainOf, evalPath, fingerprintShape, parsePath, policyScopesCapability, referencedResourceName, resolveResourceName, resourceIndex, validateSemantics }; | ||
| export { type Diagnostic, type IR, type IRConnector, type IRContract, type IRField, type IRFieldMapping, type IRPolicyRule, type IRResourceRegistry, type IRResponseMapping, type IRRestConnector, type IRTool, type IRType, JSON_TYPES, type JsonType, LIFECYCLE_STATES, type Lifecycle, type PathParse, type Resolution, SEMANTIC_TYPES, type SemanticType, type Severity, type ShapeAddition, type ShapeDiff, type ShapeMap, type ShapeRetype, compile, describeShape, diffShape, domainOf, evalPath, fingerprintShape, fingerprintShapeMap, hasShapeDrift, parsePath, policyScopesCapability, referencedResourceName, resolveResourceName, resourceIndex, shapeDriftSummary, validateSemantics }; |
+66
-5
@@ -84,2 +84,3 @@ // src/ir.ts | ||
| import { createHash } from "crypto"; | ||
| var JSON_TYPES = ["string", "number", "boolean", "null", "array", "object"]; | ||
| function jsonType(v) { | ||
@@ -102,11 +103,53 @@ if (v === null) return "null"; | ||
| } | ||
| function hashEntries(entries) { | ||
| const sorted = [...entries].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); | ||
| const canonical = JSON.stringify(sorted); | ||
| return `sha256:${createHash("sha256").update(canonical).digest("hex")}`; | ||
| } | ||
| function fingerprintShape(value) { | ||
| const entries = []; | ||
| shapeEntries(value, "$", entries); | ||
| entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); | ||
| const canonical = JSON.stringify(entries); | ||
| const hash = createHash("sha256").update(canonical).digest("hex"); | ||
| return `sha256:${hash}`; | ||
| return hashEntries(entries); | ||
| } | ||
| function describeShape(value) { | ||
| const entries = []; | ||
| shapeEntries(value, "$", entries); | ||
| return Object.fromEntries(entries); | ||
| } | ||
| function fingerprintShapeMap(shape) { | ||
| return hashEntries(Object.entries(shape)); | ||
| } | ||
| // src/shape-diff.ts | ||
| function byPath(entries) { | ||
| return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); | ||
| } | ||
| function diffShape(recorded, live) { | ||
| const added = []; | ||
| const removed = []; | ||
| const retyped = []; | ||
| for (const [path, type] of Object.entries(live)) { | ||
| const before = recorded[path]; | ||
| if (before === void 0) added.push({ path, type }); | ||
| else if (before !== type) retyped.push({ path, from: before, to: type }); | ||
| } | ||
| for (const [path, type] of Object.entries(recorded)) { | ||
| if (live[path] === void 0) removed.push({ path, type }); | ||
| } | ||
| return { added: byPath(added), removed: byPath(removed), retyped: byPath(retyped) }; | ||
| } | ||
| function hasShapeDrift(diff) { | ||
| return diff.added.length > 0 || diff.removed.length > 0 || diff.retyped.length > 0; | ||
| } | ||
| function shapeDriftSummary(diff) { | ||
| const parts = []; | ||
| const fmt = (e) => `${e.path} (${e.type})`; | ||
| if (diff.added.length > 0) parts.push(`gained ${diff.added.length} field(s): ${diff.added.map(fmt).join(", ")}`); | ||
| if (diff.removed.length > 0) parts.push(`lost ${diff.removed.length} field(s): ${diff.removed.map(fmt).join(", ")}`); | ||
| if (diff.retyped.length > 0) { | ||
| parts.push(`retyped ${diff.retyped.length} field(s): ${diff.retyped.map((e) => `${e.path} (${e.from} \u2192 ${e.to})`).join(", ")}`); | ||
| } | ||
| return parts.join("; "); | ||
| } | ||
| // src/compile.ts | ||
@@ -189,2 +232,11 @@ var CONNECTOR_TYPES = /* @__PURE__ */ new Set(["rest", "graphql", "grpc", "sql", "soap"]); | ||
| } | ||
| function lowerShape(raw) { | ||
| if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0; | ||
| const out = {}; | ||
| for (const [path, type] of Object.entries(raw)) { | ||
| if (typeof type !== "string" || !JSON_TYPES.includes(type)) return void 0; | ||
| out[path] = type; | ||
| } | ||
| return Object.keys(out).length > 0 ? out : void 0; | ||
| } | ||
| function lowerContract(raw) { | ||
@@ -194,3 +246,6 @@ if (typeof raw.fingerprint !== "string") return void 0; | ||
| if (typeof probe.fixture !== "string") return void 0; | ||
| return { fingerprint: raw.fingerprint, probeFixture: probe.fixture }; | ||
| const contract = { fingerprint: raw.fingerprint, probeFixture: probe.fixture }; | ||
| const shape = lowerShape(raw.shape); | ||
| if (shape) contract.shape = shape; | ||
| return contract; | ||
| } | ||
@@ -621,8 +676,13 @@ function policyScopesCapability(meta, capabilityId, provider) { | ||
| export { | ||
| JSON_TYPES, | ||
| LIFECYCLE_STATES, | ||
| SEMANTIC_TYPES, | ||
| compile, | ||
| describeShape, | ||
| diffShape, | ||
| domainOf, | ||
| evalPath, | ||
| fingerprintShape, | ||
| fingerprintShapeMap, | ||
| hasShapeDrift, | ||
| parsePath, | ||
@@ -633,4 +693,5 @@ policyScopesCapability, | ||
| resourceIndex, | ||
| shapeDriftSummary, | ||
| validateSemantics | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
+2
-2
| { | ||
| "name": "@archstone/compiler", | ||
| "version": "0.13.0", | ||
| "version": "0.14.0", | ||
| "private": false, | ||
@@ -41,3 +41,3 @@ "type": "module", | ||
| "jsonpath-plus": "^10.3.0", | ||
| "@archstone/schema": "0.13.0" | ||
| "@archstone/schema": "0.14.0" | ||
| }, | ||
@@ -44,0 +44,0 @@ "devDependencies": { |
Sorry, the diff of this file is too big to display
138016
12.79%972
16.13%+ Added
- Removed
Updated