@coproduct_inc/verify 0.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to `@coproduct_inc/verify`.
4
4
 
5
+ ## 0.6.0
6
+
7
+ - **The `Workflow` flow check now CONSUMES the one proven IFC model** —
8
+ `nucleus-ifc`'s `FlowDeclaration → IfcVerdict` decision, compiled into the
9
+ package's wasm and exposed as `ifcDecide` (`./ifc-decide`). The DAG walk
10
+ accumulates each node's declared inputs (`secret`, `web_content`, …) and asks
11
+ the proven `decide()` whether they may reach a sink — the same decision
12
+ nucleus's production gate runs, recomputed locally. No TS mirror in the
13
+ decision path.
14
+ - New `FlowPolicy` axes `adversarialSources` + `actionSinks` enable **indirect
15
+ prompt-injection** detection: web content (Adversarial integrity, NoAuthority)
16
+ reaching an action sink is refused across the DAG; a declassifier breaks the
17
+ path. Existing confidentiality policies are unchanged.
18
+ - **IFC lattice helpers** (`./ifc`) — a TS reflection of the same Denning lattice
19
+ for direct in-process use, kept **parity-checked against `ifcDecide`** (and the
20
+ Lean-proven laws via `ifcLatticeLaws()`).
21
+ - Build: `nucleus-wasm` now depends on `nucleus-ifc` (lattice-only, no `ring`) and
22
+ exports `ifc_decide`; the `build:wasm` script preserves the wasm dir's
23
+ `commonjs` marker so the CJS bindings load inside this ESM package.
24
+
5
25
  ## 0.5.0
6
26
 
7
27
  - **`Workflow` — the DAG cage** (`./workflow`, re-exported from root). Compose a
package/README.md CHANGED
@@ -74,6 +74,13 @@ verifyWorkflowReceipt(receipt, { sources: ["read_secret"], sinks: ["publish"] })
74
74
  It rides on whatever orchestrator you already use (LangGraph, Temporal, CrewAI…)
75
75
  — you cage the tools, not the framework. Run it: `node examples/workflow-leak.mjs`.
76
76
 
77
+ The flow check is grounded in the **Denning IFC lattice** (`./ifc`, a faithful
78
+ port of `nucleus-ifc` whose lattice laws are Lean-proven). Beyond confidentiality
79
+ (`sources`/`sinks`), the `adversarialSources` + `actionSinks` axes catch
80
+ **indirect prompt injection** — web content (Adversarial integrity, no authority
81
+ to instruct) reaching an action sink is refused across the DAG, and a
82
+ declassifier breaks the path.
83
+
77
84
  ---
78
85
 
79
86
  ## In-bounds attestation — the drop-in
@@ -0,0 +1,20 @@
1
+ /** The declared-input kinds the IFC model recognises (nucleus-ifc DeclaredInput). */
2
+ export type DeclaredInputToken = "user_prompt" | "web_content" | "tool_response" | "file_read" | "env_var" | "secret" | "database_row" | "memory_read" | "http_response";
3
+ /** A recomputable IFC verdict (the serialized `nucleus_ifc::decision::IfcVerdict`). */
4
+ export interface IfcVerdict {
5
+ allow: boolean;
6
+ reason: string;
7
+ declared_inputs: string[];
8
+ }
9
+ /**
10
+ * Recompute the proven IFC decision for an action: given the declared inputs
11
+ * reaching a sink and the sink's nature, is the flow safe? `requiresAuthority` =
12
+ * the action needs directive authority (so adversarial / no-authority inputs are
13
+ * refused — the prompt-injection guard); `sinkPublic` = the sink is publicly
14
+ * visible (so secret inputs are refused). Returns the same `IfcVerdict` nucleus's
15
+ * production gate returns — you trust the proven model, not this package.
16
+ */
17
+ export declare function ifcDecide(inputs: DeclaredInputToken[], opts?: {
18
+ requiresAuthority?: boolean;
19
+ sinkPublic?: boolean;
20
+ }): IfcVerdict;
@@ -0,0 +1,18 @@
1
+ // The ONE proven IFC decision, consumed from the real model — not a TS mirror.
2
+ // `ifc_decide` is compiled from `nucleus-ifc` (the Denning lattice + the
3
+ // FlowDeclaration→IfcVerdict decision the production gate runs; its lattice laws
4
+ // are Lean-proven in portcullis-core) into the package's wasm. The DAG-cage flow
5
+ // check (`./workflow`) delegates its verdict here, so the recomputable decision
6
+ // is the same one nucleus runs — closing the port→source gap.
7
+ import * as wasm from "../wasm/nodejs/nucleus_wasm.js";
8
+ /**
9
+ * Recompute the proven IFC decision for an action: given the declared inputs
10
+ * reaching a sink and the sink's nature, is the flow safe? `requiresAuthority` =
11
+ * the action needs directive authority (so adversarial / no-authority inputs are
12
+ * refused — the prompt-injection guard); `sinkPublic` = the sink is publicly
13
+ * visible (so secret inputs are refused). Returns the same `IfcVerdict` nucleus's
14
+ * production gate returns — you trust the proven model, not this package.
15
+ */
16
+ export function ifcDecide(inputs, opts = {}) {
17
+ return wasm.ifc_decide(JSON.stringify(inputs), !!opts.requiresAuthority, !!opts.sinkPublic);
18
+ }
package/dist/ifc.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ export declare enum ConfLevel {
2
+ Public = 0,
3
+ Internal = 1,
4
+ Secret = 2
5
+ }
6
+ export declare enum IntegLevel {
7
+ Adversarial = 0,
8
+ Untrusted = 1,
9
+ Trusted = 2
10
+ }
11
+ export declare enum AuthorityLevel {
12
+ NoAuthority = 0,
13
+ Informational = 1,
14
+ Suggestive = 2,
15
+ Directive = 3
16
+ }
17
+ export interface IFCLabel {
18
+ conf: ConfLevel;
19
+ integ: IntegLevel;
20
+ authority: AuthorityLevel;
21
+ }
22
+ /** Least-restrictive label (public, trusted, directive) — the join identity. */
23
+ export declare const BOTTOM: IFCLabel;
24
+ /** Lattice join (⊔): how labels combine as data flows together. Confidentiality
25
+ * rises (max), integrity and authority fall to the least-trusted input (min) —
26
+ * taint cannot be laundered by mixing in trusted data. */
27
+ export declare function join(a: IFCLabel, b: IFCLabel): IFCLabel;
28
+ /**
29
+ * Can data labelled `a` flow to a sink that admits up to `sink` (a ⊑ sink)?
30
+ * The sink encodes: the MAX confidentiality it may receive, and the MIN
31
+ * integrity/authority it requires. A flow is allowed iff confidentiality is not
32
+ * too high, integrity is at least required, and authority is at least required.
33
+ */
34
+ export declare function flowsTo(a: IFCLabel, sink: IFCLabel): boolean;
35
+ /** Web/scraped content: public, ADVERSARIAL integrity, NO authority to instruct. */
36
+ export declare const WEB_CONTENT: IFCLabel;
37
+ /** A read secret (API key, PII): SECRET confidentiality. */
38
+ export declare const SECRET_READ: IFCLabel;
39
+ /** A user prompt: trusted + directive (it MAY steer the agent). */
40
+ export declare const USER_PROMPT: IFCLabel;
41
+ /** A PUBLIC external sink (egress): admits only Public conf; demands no trust. */
42
+ export declare const PUBLIC_EGRESS: IFCLabel;
43
+ /** An ACTION sink that fires only on trusted, directive-authority data — the
44
+ * prompt-injection guard: adversarial / no-authority data can't drive it. */
45
+ export declare const TRUSTED_ACTION: IFCLabel;
46
+ /**
47
+ * Check the lattice laws this port relies on (the ones Lean-proven in nucleus):
48
+ * idempotent / commutative / associative join, taint-absorption on each axis,
49
+ * and the bounds. Returns the first violation, or null when all hold — so a test
50
+ * can assert parity with the proven model instead of trusting the port.
51
+ */
52
+ export declare function ifcLatticeLaws(): string | null;
package/dist/ifc.js ADDED
@@ -0,0 +1,106 @@
1
+ // A TypeScript REFLECTION of the Denning information-flow lattice from
2
+ // `nucleus-ifc` / `portcullis-core` (the production IFC model). The workflow flow
3
+ // check itself consumes the REAL compiled decision (`./ifc-decide` → wasm); these
4
+ // helpers are for direct in-process lattice work, kept parity-checked against
5
+ // that real model (see test/ifc.test.mjs) and against the Lean-proven laws via
6
+ // `ifcLatticeLaws()` (portcullis-core/lean/IFCSemilatticeProofs.lean). So this is
7
+ // a checked reflection, not a second source of truth.
8
+ //
9
+ // Three axes, each with its lattice direction:
10
+ // confidentiality — COVARIANT (join = max; Secret dominates: don't leak it out)
11
+ // integrity — CONTRAVARIANT (join = min; Adversarial dominates: Biba)
12
+ // authority — CONTRAVARIANT (join = min; NoAuthority dominates: untrusted
13
+ // data can be READ but never acquires authority to INSTRUCT —
14
+ // the formal block on indirect prompt injection)
15
+ export var ConfLevel;
16
+ (function (ConfLevel) {
17
+ ConfLevel[ConfLevel["Public"] = 0] = "Public";
18
+ ConfLevel[ConfLevel["Internal"] = 1] = "Internal";
19
+ ConfLevel[ConfLevel["Secret"] = 2] = "Secret";
20
+ })(ConfLevel || (ConfLevel = {}));
21
+ export var IntegLevel;
22
+ (function (IntegLevel) {
23
+ IntegLevel[IntegLevel["Adversarial"] = 0] = "Adversarial";
24
+ IntegLevel[IntegLevel["Untrusted"] = 1] = "Untrusted";
25
+ IntegLevel[IntegLevel["Trusted"] = 2] = "Trusted";
26
+ })(IntegLevel || (IntegLevel = {}));
27
+ export var AuthorityLevel;
28
+ (function (AuthorityLevel) {
29
+ AuthorityLevel[AuthorityLevel["NoAuthority"] = 0] = "NoAuthority";
30
+ AuthorityLevel[AuthorityLevel["Informational"] = 1] = "Informational";
31
+ AuthorityLevel[AuthorityLevel["Suggestive"] = 2] = "Suggestive";
32
+ AuthorityLevel[AuthorityLevel["Directive"] = 3] = "Directive";
33
+ })(AuthorityLevel || (AuthorityLevel = {}));
34
+ /** Least-restrictive label (public, trusted, directive) — the join identity. */
35
+ export const BOTTOM = {
36
+ conf: ConfLevel.Public,
37
+ integ: IntegLevel.Trusted,
38
+ authority: AuthorityLevel.Directive,
39
+ };
40
+ /** Lattice join (⊔): how labels combine as data flows together. Confidentiality
41
+ * rises (max), integrity and authority fall to the least-trusted input (min) —
42
+ * taint cannot be laundered by mixing in trusted data. */
43
+ export function join(a, b) {
44
+ return {
45
+ conf: Math.max(a.conf, b.conf),
46
+ integ: Math.min(a.integ, b.integ),
47
+ authority: Math.min(a.authority, b.authority),
48
+ };
49
+ }
50
+ /**
51
+ * Can data labelled `a` flow to a sink that admits up to `sink` (a ⊑ sink)?
52
+ * The sink encodes: the MAX confidentiality it may receive, and the MIN
53
+ * integrity/authority it requires. A flow is allowed iff confidentiality is not
54
+ * too high, integrity is at least required, and authority is at least required.
55
+ */
56
+ export function flowsTo(a, sink) {
57
+ return a.conf <= sink.conf && a.integ >= sink.integ && a.authority >= sink.authority;
58
+ }
59
+ // ── node-kind presets (mirror nucleus-ifc's NodeKind labelling) ──────────────
60
+ /** Web/scraped content: public, ADVERSARIAL integrity, NO authority to instruct. */
61
+ export const WEB_CONTENT = { conf: ConfLevel.Public, integ: IntegLevel.Adversarial, authority: AuthorityLevel.NoAuthority };
62
+ /** A read secret (API key, PII): SECRET confidentiality. */
63
+ export const SECRET_READ = { conf: ConfLevel.Secret, integ: IntegLevel.Trusted, authority: AuthorityLevel.NoAuthority };
64
+ /** A user prompt: trusted + directive (it MAY steer the agent). */
65
+ export const USER_PROMPT = { conf: ConfLevel.Internal, integ: IntegLevel.Trusted, authority: AuthorityLevel.Directive };
66
+ /** A PUBLIC external sink (egress): admits only Public conf; demands no trust. */
67
+ export const PUBLIC_EGRESS = { conf: ConfLevel.Public, integ: IntegLevel.Adversarial, authority: AuthorityLevel.NoAuthority };
68
+ /** An ACTION sink that fires only on trusted, directive-authority data — the
69
+ * prompt-injection guard: adversarial / no-authority data can't drive it. */
70
+ export const TRUSTED_ACTION = { conf: ConfLevel.Secret, integ: IntegLevel.Untrusted, authority: AuthorityLevel.Directive };
71
+ /**
72
+ * Check the lattice laws this port relies on (the ones Lean-proven in nucleus):
73
+ * idempotent / commutative / associative join, taint-absorption on each axis,
74
+ * and the bounds. Returns the first violation, or null when all hold — so a test
75
+ * can assert parity with the proven model instead of trusting the port.
76
+ */
77
+ export function ifcLatticeLaws() {
78
+ const labels = [];
79
+ for (const c of [ConfLevel.Public, ConfLevel.Internal, ConfLevel.Secret])
80
+ for (const i of [IntegLevel.Adversarial, IntegLevel.Untrusted, IntegLevel.Trusted])
81
+ for (const au of [AuthorityLevel.NoAuthority, AuthorityLevel.Informational, AuthorityLevel.Suggestive, AuthorityLevel.Directive])
82
+ labels.push({ conf: c, integ: i, authority: au });
83
+ const eq = (x, y) => x.conf === y.conf && x.integ === y.integ && x.authority === y.authority;
84
+ for (const a of labels) {
85
+ if (!eq(join(a, a), a))
86
+ return "join not idempotent";
87
+ if (!eq(join(a, BOTTOM), a))
88
+ return "BOTTOM is not the identity";
89
+ // taint-absorption: mixing in the most-tainted value on each axis dominates
90
+ if (join(a, WEB_CONTENT).integ !== IntegLevel.Adversarial)
91
+ return "adversarial integrity does not absorb";
92
+ if (join(a, SECRET_READ).conf !== ConfLevel.Secret)
93
+ return "secret confidentiality does not absorb";
94
+ if (join(a, WEB_CONTENT).authority !== AuthorityLevel.NoAuthority)
95
+ return "no-authority does not absorb";
96
+ for (const b of labels) {
97
+ if (!eq(join(a, b), join(b, a)))
98
+ return "join not commutative";
99
+ for (const c of labels) {
100
+ if (!eq(join(join(a, b), c), join(a, join(b, c))))
101
+ return "join not associative";
102
+ }
103
+ }
104
+ }
105
+ return null;
106
+ }
package/dist/index.d.ts CHANGED
@@ -96,6 +96,8 @@ export declare function verifySignature(receiptJson: string, jwksJson: string):
96
96
  export * from "./attestation.js";
97
97
  export * from "./toolproxy.js";
98
98
  export * from "./cage.js";
99
+ export * from "./ifc-decide.js";
100
+ export * from "./ifc.js";
99
101
  export * from "./workflow.js";
100
102
  export * from "./license.js";
101
103
  export * from "./claim.js";
package/dist/index.js CHANGED
@@ -73,6 +73,11 @@ export * from "./toolproxy.js";
73
73
  // The 5-minute drop-in: a runtime `Cage` wraps an agent's tool calls (enforce +
74
74
  // record) and emits a signed receipt the lines above verify. See `./cage`.
75
75
  export * from "./cage.js";
76
+ // The ONE proven IFC decision, consumed from nucleus-ifc via wasm (`ifcDecide`).
77
+ export * from "./ifc-decide.js";
78
+ // The Denning lattice helpers (a faithful TS reflection of the same model, kept
79
+ // parity-checked against `ifcDecide`) for direct in-process use.
80
+ export * from "./ifc.js";
76
81
  // The DAG cage: compose a multi-agent run into one verifiable `WorkflowReceipt`,
77
82
  // including the cross-node information flow ("locally fine, globally leaks").
78
83
  export * from "./workflow.js";
@@ -27,14 +27,25 @@ export interface WorkflowReceipt {
27
27
  signature: string;
28
28
  signedAt?: string;
29
29
  }
30
- /** An information-flow policy checked across the whole DAG. */
30
+ /**
31
+ * An information-flow policy checked across the whole DAG, grounded in the
32
+ * Denning lattice (`./ifc`, a faithful port of nucleus-ifc — Lean-proven laws).
33
+ * `sources`/`sinks` are the confidentiality axis (don't leak a secret to a
34
+ * public sink); the optional axes cover integrity / indirect prompt injection.
35
+ */
31
36
  export interface FlowPolicy {
32
- /** Tools that introduce taint (e.g. reading a secret). */
37
+ /** Tools that read SECRET data (raise confidentiality). */
33
38
  sources: string[];
34
- /** Tools that must never be reached while tainted (e.g. an external write). */
39
+ /** Public-egress tools a secret must never reach. */
35
40
  sinks: string[];
36
- /** Nodes that sanitize — they clear incoming taint (a sanitizer in the chain). */
41
+ /** Nodes that sanitize — they clear incoming taint (a declassifier). */
37
42
  declassifiers?: string[];
43
+ /** Tools that introduce ADVERSARIAL integrity (e.g. web fetch) — the indirect
44
+ * prompt-injection taint. */
45
+ adversarialSources?: string[];
46
+ /** Action tools that must only fire on trusted, directive-authority data —
47
+ * adversarial / no-authority data reaching them is a leak. */
48
+ actionSinks?: string[];
38
49
  }
39
50
  export interface FlowLeak {
40
51
  node: string;
package/dist/workflow.js CHANGED
@@ -21,6 +21,7 @@ import { createHash, createPublicKey, sign as edSign, verify as edVerify } from
21
21
  import { Cage } from "./cage.js";
22
22
  import { canonicalize, exportPublicKey } from "./attestation.js";
23
23
  import { traceEventsFromRecords } from "./toolproxy.js";
24
+ import { ifcDecide } from "./ifc-decide.js";
24
25
  const WF_KIND = "nucleus.workflow-receipt.v1";
25
26
  function sha256Hex(s) {
26
27
  return createHash("sha256").update(s).digest("hex");
@@ -60,26 +61,47 @@ function topoOrder(nodes) {
60
61
  throw new Error("workflow graph has a cycle");
61
62
  return order;
62
63
  }
63
- /** Recompute information-flow taint across the DAG → the leaks (independent). */
64
+ /**
65
+ * Recompute the information flow across the DAG, delegating each sink's VERDICT
66
+ * to the proven model (`ifcDecide`, compiled from nucleus-ifc). The DAG walk
67
+ * accumulates the declared-input KINDS reaching each node (a `source` tool
68
+ * declares a `secret` input, an `adversarialSource` declares `web_content`); a
69
+ * declassifier clears them; at a sink we ask the proven `decide()` whether that
70
+ * input set may reach that sink. So the topology is ours, the IFC decision is
71
+ * the one nucleus runs — not a TS mirror.
72
+ */
64
73
  function recomputeFlow(nodes, flow) {
65
74
  const sources = new Set(flow.sources);
75
+ const adversarial = new Set(flow.adversarialSources ?? []);
66
76
  const sinks = new Set(flow.sinks);
77
+ const actionSinks = new Set(flow.actionSinks ?? []);
67
78
  const declass = new Set(flow.declassifiers ?? []);
68
79
  const byId = new Map(nodes.map((n) => [n.id, n]));
69
- const tainted = new Map();
80
+ const inputsAt = new Map();
70
81
  const leaks = [];
71
82
  for (const id of topoOrder(nodes)) {
72
83
  const n = byId.get(id);
73
84
  const tools = new Set(n.events.map((e) => e.tool));
74
- const readsSource = [...tools].some((t) => sources.has(t));
75
- const parentTainted = n.after.some((p) => tainted.get(p));
76
- const isTainted = declass.has(id) ? false : readsSource || parentTainted;
77
- tainted.set(id, isTainted);
78
- if (isTainted) {
79
- for (const t of tools) {
80
- if (sinks.has(t))
81
- leaks.push({ node: id, sink: t, taintedVia: readsSource ? "reads-source" : "upstream" });
82
- }
85
+ const acc = new Set();
86
+ for (const p of n.after)
87
+ for (const t of inputsAt.get(p))
88
+ acc.add(t); // inherit upstream inputs
89
+ const readsSecret = [...tools].some((t) => sources.has(t));
90
+ const readsWeb = [...tools].some((t) => adversarial.has(t));
91
+ if (readsSecret)
92
+ acc.add("secret");
93
+ if (readsWeb)
94
+ acc.add("web_content");
95
+ if (declass.has(id))
96
+ acc.clear(); // a declassifier sanitizes incoming + own inputs
97
+ inputsAt.set(id, acc);
98
+ const declared = [...acc];
99
+ const via = (readsSecret || readsWeb) && !declass.has(id) ? "reads-source" : "upstream";
100
+ for (const t of tools) {
101
+ if (sinks.has(t) && !ifcDecide(declared, { sinkPublic: true }).allow)
102
+ leaks.push({ node: id, sink: t, taintedVia: via });
103
+ if (actionSinks.has(t) && !ifcDecide(declared, { requiresAuthority: true }).allow)
104
+ leaks.push({ node: id, sink: t, taintedVia: via });
83
105
  }
84
106
  }
85
107
  return leaks;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coproduct_inc/verify",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Offline, zero-trust verification of agent capability-boundary in-bounds attestation receipts: emit and verify an Ed25519-signed, hash-chained receipt that an agent run stayed within a declared boundary, keyed on a cryptographic principal (not a mutable display name) \u2014 the evidence layer above probabilistic guardrails. Also verifies Nucleus auction receipts by re-running the clearing locally. Zero runtime deps for the attestation core (node:crypto).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,6 +21,10 @@
21
21
  "types": "./dist/workflow.d.ts",
22
22
  "import": "./dist/workflow.js"
23
23
  },
24
+ "./ifc": {
25
+ "types": "./dist/ifc.d.ts",
26
+ "import": "./dist/ifc.js"
27
+ },
24
28
  "./attestation": {
25
29
  "types": "./dist/attestation.d.ts",
26
30
  "import": "./dist/attestation.js"
@@ -57,7 +61,7 @@
57
61
  "CHANGELOG.md"
58
62
  ],
59
63
  "scripts": {
60
- "build:wasm": "wasm-pack build ../../crates/nucleus-wasm --target nodejs --out-dir ../../packages/nucleus-verify/wasm/nodejs && rm -f wasm/nodejs/.gitignore wasm/nodejs/package.json",
64
+ "build:wasm": "wasm-pack build ../../crates/nucleus-wasm --target nodejs --out-dir ../../packages/nucleus-verify/wasm/nodejs && rm -f wasm/nodejs/.gitignore && printf '%s' '{\"type\":\"commonjs\"}' > wasm/nodejs/package.json",
61
65
  "build": "tsc -p tsconfig.json",
62
66
  "typecheck": "tsc -p tsconfig.json --noEmit",
63
67
  "test": "node --test",
@@ -51,6 +51,21 @@ export function compute_dependency_graph(chain_jsonl: string): any;
51
51
  */
52
52
  export function compute_savings_summary(plan_json: string, cost_map_json: string): any;
53
53
 
54
+ /**
55
+ * **The ONE proven IFC decision, surfaced to JS.** Recomputes the
56
+ * `FlowDeclaration → IfcVerdict` decision over the Denning lattice from
57
+ * `nucleus-ifc` (whose lattice laws are Lean-proven in `portcullis-core`) — the
58
+ * SAME `decide()` the production gate runs. `@coproduct_inc/verify`'s DAG-cage
59
+ * flow check delegates here instead of a TS mirror, so there is one model.
60
+ *
61
+ * `input_tokens_json` is a JSON array of declared-input tokens — one of:
62
+ * `user_prompt`, `web_content`, `tool_response`, `file_read`, `env_var`,
63
+ * `secret`, `database_row`, `memory_read`, `http_response`. `requires_authority`
64
+ * = the action needs directive authority; `sink_public` = the sink is publicly
65
+ * visible. Returns the recomputable `IfcVerdict` (allow/deny + reason).
66
+ */
67
+ export function ifc_decide(input_tokens_json: string, requires_authority: boolean, sink_public: boolean): any;
68
+
54
69
  /**
55
70
  * Install a browser-friendly panic hook. The hook routes any wasm-side
56
71
  * `panic!` into `console.error`, which means failed assertions in the
@@ -89,6 +89,34 @@ function compute_savings_summary(plan_json, cost_map_json) {
89
89
  }
90
90
  exports.compute_savings_summary = compute_savings_summary;
91
91
 
92
+ /**
93
+ * **The ONE proven IFC decision, surfaced to JS.** Recomputes the
94
+ * `FlowDeclaration → IfcVerdict` decision over the Denning lattice from
95
+ * `nucleus-ifc` (whose lattice laws are Lean-proven in `portcullis-core`) — the
96
+ * SAME `decide()` the production gate runs. `@coproduct_inc/verify`'s DAG-cage
97
+ * flow check delegates here instead of a TS mirror, so there is one model.
98
+ *
99
+ * `input_tokens_json` is a JSON array of declared-input tokens — one of:
100
+ * `user_prompt`, `web_content`, `tool_response`, `file_read`, `env_var`,
101
+ * `secret`, `database_row`, `memory_read`, `http_response`. `requires_authority`
102
+ * = the action needs directive authority; `sink_public` = the sink is publicly
103
+ * visible. Returns the recomputable `IfcVerdict` (allow/deny + reason).
104
+ * @param {string} input_tokens_json
105
+ * @param {boolean} requires_authority
106
+ * @param {boolean} sink_public
107
+ * @returns {any}
108
+ */
109
+ function ifc_decide(input_tokens_json, requires_authority, sink_public) {
110
+ const ptr0 = passStringToWasm0(input_tokens_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
111
+ const len0 = WASM_VECTOR_LEN;
112
+ const ret = wasm.ifc_decide(ptr0, len0, requires_authority, sink_public);
113
+ if (ret[2]) {
114
+ throw takeFromExternrefTable0(ret[1]);
115
+ }
116
+ return takeFromExternrefTable0(ret[0]);
117
+ }
118
+ exports.ifc_decide = ifc_decide;
119
+
92
120
  /**
93
121
  * Install a browser-friendly panic hook. The hook routes any wasm-side
94
122
  * `panic!` into `console.error`, which means failed assertions in the
Binary file
@@ -4,6 +4,7 @@ export const memory: WebAssembly.Memory;
4
4
  export const analyze_correction_event: (a: number, b: number, c: number, d: number) => [number, number, number];
5
5
  export const compute_dependency_graph: (a: number, b: number) => [number, number, number];
6
6
  export const compute_savings_summary: (a: number, b: number, c: number, d: number) => [number, number, number];
7
+ export const ifc_decide: (a: number, b: number, c: number, d: number) => [number, number, number];
7
8
  export const parse_claude_code_session: (a: number, b: number) => [number, number, number];
8
9
  export const parse_lineage_chain: (a: number, b: number) => [number, number, number];
9
10
  export const verify_auction_receipt: (a: number, b: number, c: number, d: number) => [number, number, number];
@@ -1,9 +1 @@
1
- {
2
- "name": "nucleus-wasm-nodejs-core",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "commonjs",
6
- "main": "nucleus_wasm.js",
7
- "types": "nucleus_wasm.d.ts",
8
- "comment": "wasm-pack `nodejs` target (CommonJS). This nested package.json pins `type: commonjs` so the CJS `require`/`module.exports`/`__dirname` glue keeps working when imported from the ESM `@nucleus/verify` parent. Regenerated by `pnpm build:wasm`; do not hand-edit the sibling .js/.wasm."
9
- }
1
+ {"type":"commonjs"}