@sema-agent/core 5.64.0 → 6.0.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 +70 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +113 -4
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +2 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +48 -8
- package/dist/core/hooks.js +39 -22
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +6 -0
- package/dist/core/memory-engine/engine.d.ts +329 -15
- package/dist/core/memory-engine/engine.js +364 -34
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +80 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +20 -0
- package/dist/core/permission-rule-consent.js +12 -3
- package/dist/core/permission-rule-model.d.ts +67 -7
- package/dist/core/permission-rule-model.js +53 -7
- package/dist/core/permission-rule-store.js +15 -10
- package/dist/core/permission-rule-sync.js +15 -11
- package/dist/core/remote-env.d.ts +3 -3
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +119 -5
- package/dist/core/runner/prepare-hands-readface.js +103 -8
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +306 -25
- package/dist/core/runner/prepare-task.d.ts +156 -5
- package/dist/core/runner/prepare-task.js +488 -98
- package/dist/core/runner/runtask.d.ts +27 -20
- package/dist/core/runner/runtask.js +283 -99
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/stub-env.d.ts +4 -0
- package/dist/core/stub-env.js +1 -0
- package/dist/core/task-registry-shared.js +30 -2
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/types.d.ts +875 -97
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +17 -0
- package/dist/core/workflow-run-store.d.ts +20 -0
- package/dist/core/workflow-run-store.js +1 -0
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +44 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +14 -6
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +112 -1
- package/dist/orchestration/workflow-types.js +2 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +182 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +15 -7
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.js +1 -0
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +159 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -490,6 +490,20 @@ export declare function deriveProductSlug(name: string | undefined, fallback: st
|
|
|
490
490
|
export declare class ConsolidationRefusedError extends Error {
|
|
491
491
|
readonly code: string;
|
|
492
492
|
readonly reasons: string[];
|
|
493
|
+
/**
|
|
494
|
+
* #495 — THIS refusal's own occurrence identity, minted where the fact is created (one throw, one
|
|
495
|
+
* id) rather than at whatever face later describes it. The notice dialect
|
|
496
|
+
* ({@link import("./engine.js").memoryConsolidationRefusedNotice}) reads it off the error, so the
|
|
497
|
+
* throw and its announcement name the same event however many times a host renders it: a repeated
|
|
498
|
+
* gate refusal (same `code`, same `scope`, byte-identical `message`) is otherwise indistinguishable
|
|
499
|
+
* from one refusal logged twice.
|
|
500
|
+
*
|
|
501
|
+
* Per-occurrence by construction, and correctly so: a refusal is a pure in-process event with no
|
|
502
|
+
* durable seat — nothing persists it, so nothing replays it, and two refusals ARE two facts. (The
|
|
503
|
+
* fail-open shape #476 rejected is a per-DELIVERY mint for families whose occurrences do return
|
|
504
|
+
* from a durable store and must answer the same id each time; this is the opposite geometry.)
|
|
505
|
+
*/
|
|
506
|
+
readonly occurrenceId: string;
|
|
493
507
|
constructor(code: string, message: string, reasons?: string[]);
|
|
494
508
|
}
|
|
495
509
|
/** A NotePatch list for one frozen product (the §1.3 add-only arm's one spelling). */
|
|
@@ -526,16 +540,25 @@ export declare function productAddPatch(product: MemoryEntry): NotePatch;
|
|
|
526
540
|
* influence); committed cycles stand (add-only, no rollback), and the host
|
|
527
541
|
* re-runs to re-mint over the current bytes (immediately or next window —
|
|
528
542
|
* host policy).
|
|
543
|
+
* - `purity_violation` — design/383 §1.3 (S-1): the minted plan carries a product that will
|
|
544
|
+
* commit UNMARKED while containing bytes only the MARKED arm was shown. The
|
|
545
|
+
* exposure partition's isolation premise is falsified, so the whole mint is
|
|
546
|
+
* discarded with ZERO writes and the transcript is archived (a security
|
|
547
|
+
* event's only evidence). DELIBERATELY DISTINCT from `driver_failed`: that
|
|
548
|
+
* one says the model half could not finish, this one says the safety
|
|
549
|
+
* topology did not hold — a host that folds them loses the ability to tell
|
|
550
|
+
* "the model was flaky" from "content crossed a boundary it must not cross",
|
|
551
|
+
* and only the second is an incident.
|
|
529
552
|
* Extending this set is a consumer-visible closed-set expansion (release-note duty: every
|
|
530
553
|
* downstream that pinned the enumeration becomes a named party — `mint_invalidated` was the
|
|
531
|
-
* design/376-C1 add, named on its release).
|
|
554
|
+
* design/376-C1 add, `purity_violation` the design/383 片1 add, each named on its release).
|
|
532
555
|
*
|
|
533
556
|
* The type DERIVES from the array (never `satisfies` beside it): the array exists precisely for
|
|
534
557
|
* downstream enumeration, and two independent declarations would let a new union arm skip the
|
|
535
558
|
* array with no compile error — the derived form makes an edit to either side a type error at
|
|
536
559
|
* the other.
|
|
537
560
|
*/
|
|
538
|
-
export declare const CONSOLIDATION_RUN_STOP_REASONS: readonly ["converged", "fuse_residue", "max_cycles", "parked", "refused", "driver_failed", "mint_invalidated"];
|
|
561
|
+
export declare const CONSOLIDATION_RUN_STOP_REASONS: readonly ["converged", "fuse_residue", "max_cycles", "parked", "refused", "driver_failed", "mint_invalidated", "purity_violation"];
|
|
539
562
|
/** See the doc on {@link CONSOLIDATION_RUN_STOP_REASONS} — the single source this derives from. */
|
|
540
563
|
export type ConsolidationRunStopReason = (typeof CONSOLIDATION_RUN_STOP_REASONS)[number];
|
|
541
564
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, renameSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { uuidv7 } from "../../engine/session/uuid.js";
|
|
3
4
|
import { atomicWriteFileSync, lockedStrictUpdate, readStrictSidecar, ControlPlaneCorruptError, ensureDirExists } from "./layout.js";
|
|
4
5
|
export function deriveSupersededSet(headers, opts = {}) {
|
|
5
6
|
const byId = new Map();
|
|
@@ -353,13 +354,15 @@ export function deriveProductSlug(name, fallback) {
|
|
|
353
354
|
export class ConsolidationRefusedError extends Error {
|
|
354
355
|
code;
|
|
355
356
|
reasons;
|
|
357
|
+
occurrenceId;
|
|
356
358
|
constructor(code, message, reasons = []) {
|
|
357
359
|
super(reasons.length > 0 ? `${message}\n${reasons.map((r) => ` - ${r}`).join("\n")}` : message);
|
|
358
360
|
this.code = code;
|
|
359
361
|
this.reasons = reasons;
|
|
362
|
+
this.occurrenceId = uuidv7();
|
|
360
363
|
}
|
|
361
364
|
}
|
|
362
365
|
export function productAddPatch(product) {
|
|
363
366
|
return { op: "add", id: product.id, entry: product, guard: "absent" };
|
|
364
367
|
}
|
|
365
|
-
export const CONSOLIDATION_RUN_STOP_REASONS = ["converged", "fuse_residue", "max_cycles", "parked", "refused", "driver_failed", "mint_invalidated"];
|
|
368
|
+
export const CONSOLIDATION_RUN_STOP_REASONS = ["converged", "fuse_residue", "max_cycles", "parked", "refused", "driver_failed", "mint_invalidated", "purity_violation"];
|
|
@@ -380,12 +380,95 @@ export declare const normalizedCandidateRev: (rev: unknown) => string;
|
|
|
380
380
|
* transcript labels — byte-identical to the plain mint modulo the attestation echo and the
|
|
381
381
|
* roster (C1-G1's identity claim).
|
|
382
382
|
* - Either arm failing fails the WHOLE mint (one mint, one verdict; both arms' calls ride the
|
|
383
|
-
* failure transcript). This widens nothing: under the run-level mint
|
|
383
|
+
* failure transcript). This widens nothing: under the run-level mint an adversarial marked row
|
|
384
384
|
* already sat inside the single grouping call and could fail it whole.
|
|
385
385
|
* - Cross-arm group structure is impossible by construction: each arm's `sanitizeLlmGroups`
|
|
386
386
|
* valid map is built over that arm's own 1..N numbering.
|
|
387
387
|
*/
|
|
388
388
|
export declare function mintExposurePartitionedPlan(args: Parameters<typeof mintLlmConsolidationPlan>[0]): Promise<MintLlmConsolidationPlanResult>;
|
|
389
|
+
/**
|
|
390
|
+
* The detector's frozen algorithm parameters — a CONTRACT SIBLING, published beside the readings
|
|
391
|
+
* so a run's verdict can be re-derived from the archive alone, and deliberately NOT a knob: this
|
|
392
|
+
* is a safety gate, and a safety gate with an off switch is a safety gate that gets switched off
|
|
393
|
+
* under deadline pressure. A new parameter set is a NEW `version`, exactly like the prompt
|
|
394
|
+
* contract's own rule.
|
|
395
|
+
*/
|
|
396
|
+
export interface MemoryDistillerPurityContract {
|
|
397
|
+
version: string;
|
|
398
|
+
/** Unicode normalization applied to BOTH sides before comparison (so a decomposed copy of a
|
|
399
|
+
* composed original still compares equal). */
|
|
400
|
+
normalization: "NFC";
|
|
401
|
+
/** Case is PRESERVED. Case-folding would widen the detector toward paraphrase matching and start
|
|
402
|
+
* minting false positives on ordinary prose — this gate answers "were these bytes copied", and
|
|
403
|
+
* a case change is already outside "verbatim". */
|
|
404
|
+
caseFold: false;
|
|
405
|
+
/** Shingle width, in whitespace-separated words: the comparison unit on every side. */
|
|
406
|
+
shingleWords: number;
|
|
407
|
+
/** A shingle shorter than this many characters is dropped before comparison (the floor that
|
|
408
|
+
* keeps short, high-frequency phrases out of the evidence set). */
|
|
409
|
+
minShingleChars: number;
|
|
410
|
+
}
|
|
411
|
+
export declare const MEMORY_DISTILLER_PURITY_CONTRACT_V1: MemoryDistillerPurityContract;
|
|
412
|
+
/** One witness: a marked-side-unique shingle found verbatim in a clean product's own bytes. */
|
|
413
|
+
export interface CleanArmLeakFinding {
|
|
414
|
+
/** The product's INDEX in `plan.products` — the locator safe to put in a message. Unlike
|
|
415
|
+
* {@link productKey} it is harness-assigned, so it cannot itself carry a payload. */
|
|
416
|
+
productIndex: number;
|
|
417
|
+
/** The product's key AS THE MODEL WROTE IT (`sanitizeLlmGroups` passes `String(g.key)` through
|
|
418
|
+
* unbounded). Useful when reading the archive; NEVER safe to interpolate into a notice, an
|
|
419
|
+
* announcement or a log line that reaches a model context — the key is model-authored text and
|
|
420
|
+
* can carry the same payload the shingle does. Use {@link productIndex} there. */
|
|
421
|
+
productKey: string;
|
|
422
|
+
field: "name" | "description" | "body";
|
|
423
|
+
/** The offending shingle VERBATIM — the evidence, not a description of it. Same rule as
|
|
424
|
+
* {@link productKey}: this is for the archive and an operator's own eyes, never for a message
|
|
425
|
+
* that lands in front of a model. */
|
|
426
|
+
shingle: string;
|
|
427
|
+
}
|
|
428
|
+
export interface CleanArmLeakVerdict {
|
|
429
|
+
/** False ⇔ the gate had nothing to check (see {@link notEngagedReason}); never a pass claim. */
|
|
430
|
+
engaged: boolean;
|
|
431
|
+
notEngagedReason?: string;
|
|
432
|
+
/** Size of the marked-side-unique evidence set (clean-arm bytes already subtracted). */
|
|
433
|
+
markedUniqueShingles: number;
|
|
434
|
+
/** How many products were scanned (the all-clean-input products of a partitioned plan). */
|
|
435
|
+
scannedProducts: number;
|
|
436
|
+
findings: CleanArmLeakFinding[];
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* design/383 §1.3 (S-1) — a VERBATIM-LEAK DETECTOR over a minted exposure-partitioned plan. Pure
|
|
440
|
+
* bytes, zero model calls, run in the driver between the mint and the plan cache.
|
|
441
|
+
*
|
|
442
|
+
* WHAT IT ASKS: did any byte sequence that ONLY the marked arm was shown come back out inside a
|
|
443
|
+
* product that will commit UNMARKED? The prompt-side audit (C1-G5) proves the clean arm's prompt
|
|
444
|
+
* carried no marked bytes; this proves the partition machine itself was not bypassed, mis-wired or
|
|
445
|
+
* side-channelled — a different failure surface, which is why both gates ship.
|
|
446
|
+
*
|
|
447
|
+
* HOW:
|
|
448
|
+
* 1. evidence source = the MARKED arm's verbatim grouping prompt (`minting.arms[marked]`), which
|
|
449
|
+
* is the exact rendered form the model saw — `name — description\n body`. Reading only entry
|
|
450
|
+
* bodies would miss a payload parked in a `description`;
|
|
451
|
+
* 2. minus the CLEAN arm's rendered bytes ⇒ the MARKED-SIDE-UNIQUE set. Text both sides legitimately
|
|
452
|
+
* carry (the shared instruction template, a description a clean sibling also holds) proves
|
|
453
|
+
* nothing and is subtracted here — this step is what keeps the gate off false positives;
|
|
454
|
+
* 3. scanned surface = the all-clean-input products' `name` / `description` / `body`. "Clean" is
|
|
455
|
+
* read off the plan's own served roster (no marked input id), which is the same judgment that
|
|
456
|
+
* decides the product commits without an origin marker. A plan that claims the attestation but
|
|
457
|
+
* carries NO roster (no plan this driver mints, but a foreign minter could stamp one) has every
|
|
458
|
+
* product read as clean — including the marked arm's own, which legitimately holds marked
|
|
459
|
+
* bytes — so it refuses. That is the intended direction: an attestation with no evidence behind
|
|
460
|
+
* it should not buy a plan the per-product fold;
|
|
461
|
+
* 4. any intersection ⇒ the caller fails the mint WHOLE. A leak falsifies the clean arm's
|
|
462
|
+
* isolation premise, and the other products of the same mint rest on that same premise.
|
|
463
|
+
*
|
|
464
|
+
* HONEST BOUNDARY (do not let this JSDoc be read as more than it is): this catches VERBATIM
|
|
465
|
+
* copying. It does not catch paraphrase, translation, or a summary of marked content — a complete
|
|
466
|
+
* detector would need a semantic judge, i.e. a model judging a model, which this design forbids.
|
|
467
|
+
* A leak shorter than one shingle (see the contract's `shingleWords`/`minShingleChars`) is below
|
|
468
|
+
* the floor and will not be seen. It is a leak DETECTOR, not a purity PROOF; `engaged: false` is
|
|
469
|
+
* "nothing to check here", never "checked and clean".
|
|
470
|
+
*/
|
|
471
|
+
export declare function detectCleanArmVerbatimLeak(plan: LlmConsolidationPlan, contract?: MemoryDistillerPurityContract): CleanArmLeakVerdict;
|
|
389
472
|
/** The distiller signature the drive consumes: `(candidates, snapshot, scope) -> proposal`.
|
|
390
473
|
* At runtime only `entry.id` is load-bearing for the replay filter — test/bench stubs that hand
|
|
391
474
|
* partial entry shapes are tolerated by the reads, the type states the engine's real snapshot.
|
|
@@ -459,6 +459,74 @@ export async function mintExposurePartitionedPlan(args) {
|
|
|
459
459
|
},
|
|
460
460
|
};
|
|
461
461
|
}
|
|
462
|
+
export const MEMORY_DISTILLER_PURITY_CONTRACT_V1 = Object.freeze({
|
|
463
|
+
version: "pu-1",
|
|
464
|
+
normalization: "NFC",
|
|
465
|
+
caseFold: false,
|
|
466
|
+
shingleWords: 6,
|
|
467
|
+
minShingleChars: 24,
|
|
468
|
+
});
|
|
469
|
+
function purityContractRefusal(message) {
|
|
470
|
+
const e = new Error(message);
|
|
471
|
+
e.code = "config.memory_distiller_purity_contract";
|
|
472
|
+
return e;
|
|
473
|
+
}
|
|
474
|
+
function screenPurityContract(c) {
|
|
475
|
+
if (typeof c?.version !== "string" || c.version.trim() === "")
|
|
476
|
+
throw purityContractRefusal("the distiller purity contract needs a non-empty version — the verdict is published beside readings and must name the parameters it was taken under.");
|
|
477
|
+
if (c.normalization !== "NFC")
|
|
478
|
+
throw purityContractRefusal(`the distiller purity contract's normalization must be "NFC" (got ${JSON.stringify(c.normalization)}) — the two sides must normalize identically or a decomposed copy reads as clean.`);
|
|
479
|
+
if (c.caseFold !== false)
|
|
480
|
+
throw purityContractRefusal(`the distiller purity contract's caseFold must be false (got ${JSON.stringify(c.caseFold)}) — see the field's own doc.`);
|
|
481
|
+
if (!Number.isInteger(c.shingleWords) || c.shingleWords < 2)
|
|
482
|
+
throw purityContractRefusal(`the distiller purity contract's shingleWords must be an integer ≥ 2 (got ${String(c.shingleWords)}) — a shorter unit is a word-frequency match, not a copy witness.`);
|
|
483
|
+
if (!Number.isInteger(c.minShingleChars) || c.minShingleChars < 1)
|
|
484
|
+
throw purityContractRefusal(`the distiller purity contract's minShingleChars must be a positive integer (got ${String(c.minShingleChars)}) — a zero floor admits whitespace-only shingles as evidence.`);
|
|
485
|
+
return c;
|
|
486
|
+
}
|
|
487
|
+
function shinglesOf(text, c) {
|
|
488
|
+
const words = text.normalize(c.normalization).split(/\s+/).filter((w) => w !== "");
|
|
489
|
+
const out = new Set();
|
|
490
|
+
for (let i = 0; i + c.shingleWords <= words.length; i++) {
|
|
491
|
+
const s = words.slice(i, i + c.shingleWords).join(" ");
|
|
492
|
+
if (s.length >= c.minShingleChars)
|
|
493
|
+
out.add(s);
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
export function detectCleanArmVerbatimLeak(plan, contract = MEMORY_DISTILLER_PURITY_CONTRACT_V1) {
|
|
498
|
+
const c = screenPurityContract(contract);
|
|
499
|
+
const none = (notEngagedReason) => ({ engaged: false, notEngagedReason, markedUniqueShingles: 0, scannedProducts: 0, findings: [] });
|
|
500
|
+
if (plan.mintExposure !== "partitioned")
|
|
501
|
+
return none("the plan carries no exposure-partition attestation — there are no arms to compare (an un-attested plan folds run-level, where every product is marked anyway)");
|
|
502
|
+
const arms = plan.minting.arms ?? [];
|
|
503
|
+
const markedArm = arms.find((a) => a.arm === "marked");
|
|
504
|
+
const cleanArm = arms.find((a) => a.arm === "clean");
|
|
505
|
+
if (markedArm === undefined)
|
|
506
|
+
return none("no marked arm was minted — no marked entry's bytes were rendered into any call");
|
|
507
|
+
if (cleanArm === undefined)
|
|
508
|
+
return none("no clean arm was minted — every product of this plan carries a marked input and commits marked");
|
|
509
|
+
const cleanPromptShingles = shinglesOf(cleanArm.groupingPrompt, c);
|
|
510
|
+
const markedUnique = new Set();
|
|
511
|
+
for (const s of shinglesOf(markedArm.groupingPrompt, c))
|
|
512
|
+
if (!cleanPromptShingles.has(s))
|
|
513
|
+
markedUnique.add(s);
|
|
514
|
+
const markedInputIds = new Set((plan.servedCandidates ?? []).filter((r) => r.marked).map((r) => r.id));
|
|
515
|
+
const cleanProducts = plan.products.map((p, productIndex) => ({ p, productIndex })).filter(({ p }) => !(p.inputIds ?? []).some((id) => markedInputIds.has(id)));
|
|
516
|
+
const findings = [];
|
|
517
|
+
for (const { p, productIndex } of cleanProducts) {
|
|
518
|
+
for (const field of ["name", "description", "body"]) {
|
|
519
|
+
const text = p[field];
|
|
520
|
+
if (typeof text !== "string" || text === "")
|
|
521
|
+
continue;
|
|
522
|
+
for (const s of shinglesOf(text, c)) {
|
|
523
|
+
if (markedUnique.has(s))
|
|
524
|
+
findings.push({ productIndex, productKey: p.key, field, shingle: s });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return { engaged: true, markedUniqueShingles: markedUnique.size, scannedProducts: cleanProducts.length, findings };
|
|
529
|
+
}
|
|
462
530
|
export function llmPlanDistiller(plan) {
|
|
463
531
|
const products = plan?.products ?? [];
|
|
464
532
|
const mintExposure = plan?.mintExposure === "partitioned" ? "partitioned" : undefined;
|
|
@@ -63,6 +63,12 @@ export function mergeHarvestReports(a, b) {
|
|
|
63
63
|
restored: [...a.restored, ...b.restored],
|
|
64
64
|
warnings: [...a.warnings, ...b.warnings],
|
|
65
65
|
};
|
|
66
|
+
const reportId = a.reportId ?? b.reportId;
|
|
67
|
+
if (reportId !== undefined)
|
|
68
|
+
merged.reportId = reportId;
|
|
69
|
+
const parts = [...new Set([a.reportId, b.reportId].filter((x) => x !== undefined))];
|
|
70
|
+
if (parts.length > 1)
|
|
71
|
+
merged.mergedFrom = parts;
|
|
66
72
|
const incident = a.incident ?? b.incident;
|
|
67
73
|
if (incident !== undefined)
|
|
68
74
|
merged.incident = incident;
|