@sentropic/h2a-runtime 0.98.6 → 0.98.7-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-launch-args.d.ts.map +1 -1
- package/dist/agent-launch-args.js +5 -5
- package/dist/agent-launch-args.js.map +1 -1
- package/dist/agy-claude.d.ts +2 -0
- package/dist/agy-claude.d.ts.map +1 -0
- package/dist/agy-claude.js +3 -0
- package/dist/agy-claude.js.map +1 -0
- package/dist/agy-headless.d.ts +11 -0
- package/dist/agy-headless.d.ts.map +1 -0
- package/dist/agy-headless.js +104 -0
- package/dist/agy-headless.js.map +1 -0
- package/dist/attach.d.ts +2 -0
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +73 -6
- package/dist/attach.js.map +1 -1
- package/dist/auth-login.d.ts +24 -1
- package/dist/auth-login.d.ts.map +1 -1
- package/dist/auth-login.js +132 -4
- package/dist/auth-login.js.map +1 -1
- package/dist/central-mcp.d.ts +2 -0
- package/dist/central-mcp.d.ts.map +1 -1
- package/dist/central-mcp.js +90 -3
- package/dist/central-mcp.js.map +1 -1
- package/dist/claude-code-cli-isolation.d.ts +53 -0
- package/dist/claude-code-cli-isolation.d.ts.map +1 -0
- package/dist/claude-code-cli-isolation.js +277 -0
- package/dist/claude-code-cli-isolation.js.map +1 -0
- package/dist/claude-code-cli-lifecycle.d.ts +80 -0
- package/dist/claude-code-cli-lifecycle.d.ts.map +1 -0
- package/dist/claude-code-cli-lifecycle.js +402 -0
- package/dist/claude-code-cli-lifecycle.js.map +1 -0
- package/dist/claude-code-cli-runner.d.ts +89 -0
- package/dist/claude-code-cli-runner.d.ts.map +1 -0
- package/dist/claude-code-cli-runner.js +659 -0
- package/dist/claude-code-cli-runner.js.map +1 -0
- package/dist/claude-seat-input.d.ts +47 -0
- package/dist/claude-seat-input.d.ts.map +1 -0
- package/dist/claude-seat-input.js +272 -0
- package/dist/claude-seat-input.js.map +1 -0
- package/dist/claude-seat-mesh-contract.d.ts +88 -0
- package/dist/claude-seat-mesh-contract.d.ts.map +1 -0
- package/dist/claude-seat-mesh-contract.js +13 -0
- package/dist/claude-seat-mesh-contract.js.map +1 -0
- package/dist/cli-help-groups.js +1 -1
- package/dist/cli-help-groups.js.map +1 -1
- package/dist/crash-log.d.ts +112 -0
- package/dist/crash-log.d.ts.map +1 -0
- package/dist/crash-log.js +560 -0
- package/dist/crash-log.js.map +1 -0
- package/dist/delegate.d.ts +10 -0
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +29 -0
- package/dist/delegate.js.map +1 -1
- package/dist/gateway-host/claude-seat-client.d.ts +32 -0
- package/dist/gateway-host/claude-seat-client.d.ts.map +1 -0
- package/dist/gateway-host/claude-seat-client.js +38 -0
- package/dist/gateway-host/claude-seat-client.js.map +1 -0
- package/dist/gateway-host/host.d.ts +6 -0
- package/dist/gateway-host/host.d.ts.map +1 -1
- package/dist/gateway-host/host.js +24 -3
- package/dist/gateway-host/host.js.map +1 -1
- package/dist/gateway-host/live-declarations.d.ts +58 -0
- package/dist/gateway-host/live-declarations.d.ts.map +1 -0
- package/dist/gateway-host/live-declarations.js +429 -0
- package/dist/gateway-host/live-declarations.js.map +1 -0
- package/dist/gateway-host/live-github.d.ts +11 -0
- package/dist/gateway-host/live-github.d.ts.map +1 -0
- package/dist/gateway-host/live-github.js +374 -0
- package/dist/gateway-host/live-github.js.map +1 -0
- package/dist/gateway-host/live-object-store.d.ts +35 -0
- package/dist/gateway-host/live-object-store.d.ts.map +1 -0
- package/dist/gateway-host/live-object-store.js +447 -0
- package/dist/gateway-host/live-object-store.js.map +1 -0
- package/dist/gateway-host/live-registration.d.ts +162 -0
- package/dist/gateway-host/live-registration.d.ts.map +1 -0
- package/dist/gateway-host/live-registration.js +455 -0
- package/dist/gateway-host/live-registration.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +290 -23
- package/dist/index.js.map +1 -1
- package/dist/llm-mesh-accounts.d.ts +70 -4
- package/dist/llm-mesh-accounts.d.ts.map +1 -1
- package/dist/llm-mesh-accounts.js +296 -9
- package/dist/llm-mesh-accounts.js.map +1 -1
- package/dist/native-host.d.ts +1 -1
- package/dist/native-host.d.ts.map +1 -1
- package/dist/native-host.js +30 -17
- package/dist/native-host.js.map +1 -1
- package/dist/native-terminal/host.d.ts +6 -1
- package/dist/native-terminal/host.d.ts.map +1 -1
- package/dist/native-terminal/host.js +6 -25
- package/dist/native-terminal/host.js.map +1 -1
- package/dist/native-terminal/journal.d.ts +5 -0
- package/dist/native-terminal/journal.d.ts.map +1 -1
- package/dist/native-terminal/journal.js +28 -16
- package/dist/native-terminal/journal.js.map +1 -1
- package/dist/native-terminal/op.d.ts +13 -0
- package/dist/native-terminal/op.d.ts.map +1 -1
- package/dist/native-terminal/op.js +64 -5
- package/dist/native-terminal/op.js.map +1 -1
- package/dist/proc-identity.d.ts +31 -0
- package/dist/proc-identity.d.ts.map +1 -1
- package/dist/proc-identity.js +54 -0
- package/dist/proc-identity.js.map +1 -1
- package/dist/prompt-delivery.d.ts +3 -0
- package/dist/prompt-delivery.d.ts.map +1 -1
- package/dist/prompt-delivery.js +8 -3
- package/dist/prompt-delivery.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +79 -16
- package/dist/run.js.map +1 -1
- package/dist/terminal-reset.d.ts +104 -0
- package/dist/terminal-reset.d.ts.map +1 -0
- package/dist/terminal-reset.js +229 -0
- package/dist/terminal-reset.js.map +1 -0
- package/dist/tmux.d.ts +26 -4
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +60 -25
- package/dist/tmux.js.map +1 -1
- package/dist/vendor/engram-live/contracts.d.ts +825 -0
- package/dist/vendor/engram-live/contracts.js +1734 -0
- package/dist/vendor/engram-live/integration.d.ts +113 -0
- package/dist/vendor/engram-live/integration.js +254 -0
- package/dist/vendor/engram-live/pr-sync.d.ts +144 -0
- package/dist/vendor/engram-live/pr-sync.js +411 -0
- package/dist/vendor/engram-live/registration.d.ts +138 -0
- package/dist/vendor/engram-live/registration.js +358 -0
- package/dist/vendor/engram-live/retention.d.ts +56 -0
- package/dist/vendor/engram-live/retention.js +179 -0
- package/package.json +2 -2
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Vendored from rhanka/engram@545a57b8911ab108e84fa1778e0f268fe11bf1de: engram-live/integration.ts.
|
|
2
|
+
// Compiled with TypeScript 5.9.3 (ES2022/NodeNext, declarations); do not hand-edit.
|
|
3
|
+
import { type CapabilityId, type CapabilityStatus, type CoverageReport, type FactBatchDto, type GrammarCoverageDto, type LiveScope, type ValidationResult, type WorkspaceDeclarationDto } from "./contracts.js";
|
|
4
|
+
export interface GitPort {
|
|
5
|
+
/** Bounded Git read seam; operations arrive with L2a/L2b. Unused in L1b. */
|
|
6
|
+
readonly liveGitSeam: "host-git";
|
|
7
|
+
}
|
|
8
|
+
export interface FilesystemPort {
|
|
9
|
+
/** Reads a file's bytes through the injected host filesystem. */
|
|
10
|
+
readFileBytes(path: string): Promise<Uint8Array>;
|
|
11
|
+
}
|
|
12
|
+
export interface ClockPort {
|
|
13
|
+
/** Milliseconds since the Unix epoch, host-supplied and test-fakeable. */
|
|
14
|
+
now(): number;
|
|
15
|
+
}
|
|
16
|
+
export interface ContentPort {
|
|
17
|
+
/** Content classification seam; policies arrive with L4b/L5. Unused in L1b. */
|
|
18
|
+
readonly liveContentSeam: "host-content";
|
|
19
|
+
}
|
|
20
|
+
export interface FactSinkPort {
|
|
21
|
+
/** Durable receipt of a fact batch; separate from publication (spec §3). */
|
|
22
|
+
deliver(batch: FactBatchDto, fencingToken: ScopeFencingTokenDto): Promise<{
|
|
23
|
+
received: true;
|
|
24
|
+
batchId: string;
|
|
25
|
+
receiptId?: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Mandatory scope fencing token threaded from E (L6b) across every write,
|
|
30
|
+
* cursor, and publication port to S (L3a/L3b-S) (Opus Amendment 2).
|
|
31
|
+
*/
|
|
32
|
+
export interface ScopeFencingTokenDto {
|
|
33
|
+
readonly tokenId: string;
|
|
34
|
+
readonly scope: LiveScope;
|
|
35
|
+
readonly epoch: number;
|
|
36
|
+
readonly epochId: string;
|
|
37
|
+
readonly declarationRevision: number;
|
|
38
|
+
readonly projectionIncarnation: string;
|
|
39
|
+
readonly issuedAt: number;
|
|
40
|
+
readonly expiresAt: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Required S-side storage contract for L3a / L3b-S (Opus Amendment 2):
|
|
44
|
+
* Every storage write, cursor update, and generation publication MUST atomically
|
|
45
|
+
* verify the caller's ScopeFencingTokenDto against the active writer lease and
|
|
46
|
+
* storage-owned projection incarnation P inside the storage transaction.
|
|
47
|
+
*/
|
|
48
|
+
export interface StorageIncarnationPort {
|
|
49
|
+
incarnation(): string;
|
|
50
|
+
hasGeneration(incarnation: string, generationId: string): boolean;
|
|
51
|
+
validateFencingToken(token: ScopeFencingTokenDto, nowMs: number): ValidationResult;
|
|
52
|
+
}
|
|
53
|
+
export declare const REQUIRED_STORAGE_FENCING_CONTRACT: {
|
|
54
|
+
readonly enforcedByLots: readonly ["L3a", "L3b-S"];
|
|
55
|
+
readonly requireAtomicLeaseCheckOnFactWrite: true;
|
|
56
|
+
readonly requireAtomicLeaseCheckOnCursorWrite: true;
|
|
57
|
+
readonly requireAtomicLeaseCheckOnGenerationPublish: true;
|
|
58
|
+
readonly requireStorageOwnedIncarnationBinding: true;
|
|
59
|
+
readonly requireAtomicLeaseCheckOnProgressReadWrite: true;
|
|
60
|
+
readonly requireAtomicLeaseCheckOnPark: true;
|
|
61
|
+
readonly requireAtomicLeaseCheckOnRelationRepair: true;
|
|
62
|
+
readonly requireAtomicLeaseCheckOnMarkerDelete: true;
|
|
63
|
+
readonly requireSourceAndProjectionEqualityOnGenerationPublish: true;
|
|
64
|
+
readonly requireSynchronousLeaseRevocationOnShutdown: true;
|
|
65
|
+
};
|
|
66
|
+
export declare function validateScopeFencingToken(candidate: unknown, expectedScope?: LiveScope, expectedIncarnation?: string, nowMs?: number): ValidationResult;
|
|
67
|
+
export interface CachePort {
|
|
68
|
+
/** Bounded, versioned cache seam; wired by L2b/L5. Unused in L1b. */
|
|
69
|
+
readonly liveCacheSeam: "host-cache";
|
|
70
|
+
}
|
|
71
|
+
export interface GrammarRuntimePort {
|
|
72
|
+
/** Loads a tree-sitter WASM grammar from bytes (e.g. web-tree-sitter). */
|
|
73
|
+
loadLanguage(wasm: Uint8Array): Promise<unknown>;
|
|
74
|
+
}
|
|
75
|
+
export interface LiveFactoryPorts {
|
|
76
|
+
readonly git: GitPort;
|
|
77
|
+
readonly filesystem: FilesystemPort;
|
|
78
|
+
readonly clock: ClockPort;
|
|
79
|
+
readonly content: ContentPort;
|
|
80
|
+
readonly factSink: FactSinkPort;
|
|
81
|
+
readonly cache: CachePort;
|
|
82
|
+
readonly grammarRuntime: GrammarRuntimePort;
|
|
83
|
+
}
|
|
84
|
+
export interface CreateLiveFactoryOptions {
|
|
85
|
+
readonly ports: LiveFactoryPorts;
|
|
86
|
+
readonly declaration: WorkspaceDeclarationDto;
|
|
87
|
+
}
|
|
88
|
+
export interface CapabilityResult {
|
|
89
|
+
readonly capability: CapabilityId;
|
|
90
|
+
readonly state: "unavailable";
|
|
91
|
+
readonly reason: string;
|
|
92
|
+
readonly plannedLot: string;
|
|
93
|
+
readonly delivered: false;
|
|
94
|
+
}
|
|
95
|
+
export interface GrammarLoadResult {
|
|
96
|
+
readonly language: string;
|
|
97
|
+
readonly state: "available" | "unavailable";
|
|
98
|
+
readonly bytesSha256?: string;
|
|
99
|
+
readonly grammar?: unknown;
|
|
100
|
+
readonly reason?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface LiveFactory {
|
|
103
|
+
describeCapabilities(): readonly CapabilityStatus[];
|
|
104
|
+
runCapability(capability: CapabilityId): Promise<CapabilityResult>;
|
|
105
|
+
grammarCoverage(language: string): GrammarCoverageDto;
|
|
106
|
+
grammarCoverageReport(language: string): CoverageReport;
|
|
107
|
+
loadGrammar(language: string): Promise<GrammarLoadResult>;
|
|
108
|
+
validateBatch(batch: unknown): ValidationResult;
|
|
109
|
+
}
|
|
110
|
+
export declare function createLiveFactory(options: CreateLiveFactoryOptions): LiveFactory;
|
|
111
|
+
/** The advertised grammar matrix of this build, from the pinned manifest data. */
|
|
112
|
+
export declare const ADVERTISED_GRAMMAR_LANGUAGES: readonly string[];
|
|
113
|
+
export { deriveCloneIdentity, deriveWorktreeIdentity, deriveDeclarationIdentity, type DeclarationIdentityDto, type DeriveCloneIdentityInput, type DeriveWorktreeIdentityInput, type DeriveDeclarationIdentityInput, } from "./registration.js";
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Vendored from rhanka/engram@545a57b8911ab108e84fa1778e0f268fe11bf1de: engram-live/integration.ts.
|
|
2
|
+
// Compiled with TypeScript 5.9.3 (ES2022/NodeNext, declarations); do not hand-edit.
|
|
3
|
+
// @sentropic/engram-live/integration — executable neutral factory
|
|
4
|
+
// (spec/SPEC_LIVE_GIT_INDEXING.md §2–3, §11; plan/09 L1b).
|
|
5
|
+
//
|
|
6
|
+
// The factory composes injected capabilities only: Git, filesystem, clock,
|
|
7
|
+
// content, fact-sink, cache and grammar ports. It never imports the
|
|
8
|
+
// monolithic root CLI, another @sentropic/* consumer package, the h2a host,
|
|
9
|
+
// a model module or a storage-driver handle — those stay outside the neutral
|
|
10
|
+
// closure by construction and by tests/live-neutrality.test.ts.
|
|
11
|
+
//
|
|
12
|
+
// L1b supplies the package/factory seams and grammar loading. Every other
|
|
13
|
+
// capability is explicitly unavailable until its lot lands; the factory
|
|
14
|
+
// never fabricates facts, writes through the fact sink or silently
|
|
15
|
+
// degrades an unavailable capability into an empty success.
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { ADVERTISED_LANGUAGES, CAPABILITY_IDS, CAPABILITY_PLANNED_LOTS, GRAMMAR_UNAVAILABLE_REASON, NOT_IMPLEMENTED_REASON, validateFactBatch, validateWorkspaceDeclaration, } from "./contracts.js";
|
|
20
|
+
export const REQUIRED_STORAGE_FENCING_CONTRACT = {
|
|
21
|
+
enforcedByLots: ["L3a", "L3b-S"],
|
|
22
|
+
requireAtomicLeaseCheckOnFactWrite: true,
|
|
23
|
+
requireAtomicLeaseCheckOnCursorWrite: true,
|
|
24
|
+
requireAtomicLeaseCheckOnGenerationPublish: true,
|
|
25
|
+
requireStorageOwnedIncarnationBinding: true,
|
|
26
|
+
requireAtomicLeaseCheckOnProgressReadWrite: true,
|
|
27
|
+
requireAtomicLeaseCheckOnPark: true,
|
|
28
|
+
requireAtomicLeaseCheckOnRelationRepair: true,
|
|
29
|
+
requireAtomicLeaseCheckOnMarkerDelete: true,
|
|
30
|
+
requireSourceAndProjectionEqualityOnGenerationPublish: true,
|
|
31
|
+
requireSynchronousLeaseRevocationOnShutdown: true,
|
|
32
|
+
};
|
|
33
|
+
export function validateScopeFencingToken(candidate, expectedScope, expectedIncarnation, nowMs) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
if (typeof candidate !== "object" || candidate === null) {
|
|
36
|
+
return { ok: false, errors: ["fencing token must be a non-null object"] };
|
|
37
|
+
}
|
|
38
|
+
const t = candidate;
|
|
39
|
+
if (!t.scope || typeof t.scope.tenant !== "string" || typeof t.scope.clone !== "string" || typeof t.scope.worktree !== "string")
|
|
40
|
+
errors.push("fencing token requires a complete scope");
|
|
41
|
+
if (!Number.isSafeInteger(t.declarationRevision) || (t.declarationRevision ?? 0) < 1)
|
|
42
|
+
errors.push("fencing token requires declaration revision");
|
|
43
|
+
if (typeof t.epochId !== "string" || !t.epochId)
|
|
44
|
+
errors.push("fencing token requires epoch identity");
|
|
45
|
+
for (const value of [t.tokenId, t.epochId, t.projectionIncarnation, t.scope?.tenant, t.scope?.clone, t.scope?.worktree])
|
|
46
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 1024)
|
|
47
|
+
errors.push("fencing identity must be bounded");
|
|
48
|
+
if (typeof t.tokenId !== "string" || t.tokenId.length === 0) {
|
|
49
|
+
errors.push("fencing token requires a non-empty tokenId");
|
|
50
|
+
}
|
|
51
|
+
if (typeof t.epoch !== "number" || !Number.isSafeInteger(t.epoch) || t.epoch < 1) {
|
|
52
|
+
errors.push("fencing token requires a positive integer epoch");
|
|
53
|
+
}
|
|
54
|
+
if (typeof t.projectionIncarnation !== "string" || t.projectionIncarnation.length === 0) {
|
|
55
|
+
errors.push("fencing token requires a non-empty projectionIncarnation");
|
|
56
|
+
}
|
|
57
|
+
if (typeof t.issuedAt !== "number" ||
|
|
58
|
+
typeof t.expiresAt !== "number" ||
|
|
59
|
+
!Number.isFinite(t.issuedAt) ||
|
|
60
|
+
!Number.isFinite(t.expiresAt) ||
|
|
61
|
+
t.expiresAt <= t.issuedAt) {
|
|
62
|
+
errors.push("fencing token requires finite issuedAt < expiresAt");
|
|
63
|
+
}
|
|
64
|
+
if (expectedScope !== undefined && t.scope !== undefined) {
|
|
65
|
+
if (t.scope.tenant !== expectedScope.tenant ||
|
|
66
|
+
t.scope.clone !== expectedScope.clone ||
|
|
67
|
+
t.scope.worktree !== expectedScope.worktree) {
|
|
68
|
+
errors.push("fencing token scope does not match target scope");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (expectedIncarnation !== undefined &&
|
|
72
|
+
t.projectionIncarnation !== undefined &&
|
|
73
|
+
t.projectionIncarnation !== expectedIncarnation) {
|
|
74
|
+
errors.push("fencing token projectionIncarnation does not match active storage incarnation P");
|
|
75
|
+
}
|
|
76
|
+
if (nowMs !== undefined && typeof t.expiresAt === "number" && nowMs >= t.expiresAt) {
|
|
77
|
+
errors.push("fencing token is expired");
|
|
78
|
+
}
|
|
79
|
+
if (nowMs !== undefined && typeof t.issuedAt === "number" && t.issuedAt > nowMs)
|
|
80
|
+
errors.push("fencing token is not yet valid");
|
|
81
|
+
return { ok: errors.length === 0, errors };
|
|
82
|
+
}
|
|
83
|
+
function decodeJson(bytes) {
|
|
84
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
85
|
+
}
|
|
86
|
+
function sha256(bytes) {
|
|
87
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
88
|
+
}
|
|
89
|
+
function errorMessage(error) {
|
|
90
|
+
return error instanceof Error ? error.message : String(error);
|
|
91
|
+
}
|
|
92
|
+
// The pinned manifest is validated before any grammar is trusted: a
|
|
93
|
+
// manifestVersion number and a languages record whose entries pin a
|
|
94
|
+
// non-empty file and sha256 are required.
|
|
95
|
+
function isGrammarManifest(value) {
|
|
96
|
+
if (typeof value !== "object" || value === null)
|
|
97
|
+
return false;
|
|
98
|
+
const candidate = value;
|
|
99
|
+
if (typeof candidate.manifestVersion !== "number")
|
|
100
|
+
return false;
|
|
101
|
+
if (typeof candidate.languages !== "object" || candidate.languages === null || Array.isArray(candidate.languages)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return Object.values(candidate.languages).every((entry) => {
|
|
105
|
+
if (typeof entry !== "object" || entry === null)
|
|
106
|
+
return false;
|
|
107
|
+
const record = entry;
|
|
108
|
+
return typeof record.file === "string" && record.file.length > 0
|
|
109
|
+
&& typeof record.sha256 === "string" && record.sha256.length > 0;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export function createLiveFactory(options) {
|
|
113
|
+
const declarationCheck = validateWorkspaceDeclaration(options.declaration);
|
|
114
|
+
if (!declarationCheck.ok) {
|
|
115
|
+
throw new Error(`invalid live workspace declaration: ${declarationCheck.errors.join("; ")}`);
|
|
116
|
+
}
|
|
117
|
+
const ports = options.ports;
|
|
118
|
+
const distDir = dirname(fileURLToPath(import.meta.url));
|
|
119
|
+
const manifestPath = join(distDir, "..", "grammar-manifest.json");
|
|
120
|
+
const grammarDir = join(distDir, "grammars");
|
|
121
|
+
let manifestCache;
|
|
122
|
+
const loadManifest = async () => {
|
|
123
|
+
if (manifestCache)
|
|
124
|
+
return { ok: true, manifest: manifestCache };
|
|
125
|
+
let decoded;
|
|
126
|
+
try {
|
|
127
|
+
decoded = decodeJson(await ports.filesystem.readFileBytes(manifestPath));
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
reason: `the pinned grammar manifest cannot be read through the filesystem port (${errorMessage(error)})`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (!isGrammarManifest(decoded)) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
reason: "the pinned grammar manifest is invalid: a manifestVersion number and a languages record pinning file/sha256 entries are required",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
manifestCache = decoded;
|
|
142
|
+
return { ok: true, manifest: decoded };
|
|
143
|
+
};
|
|
144
|
+
const grammarLoadCache = new Map();
|
|
145
|
+
return {
|
|
146
|
+
describeCapabilities() {
|
|
147
|
+
return CAPABILITY_IDS.map((capability) => ({
|
|
148
|
+
capability,
|
|
149
|
+
state: "unavailable",
|
|
150
|
+
reason: NOT_IMPLEMENTED_REASON,
|
|
151
|
+
plannedLot: CAPABILITY_PLANNED_LOTS[capability],
|
|
152
|
+
}));
|
|
153
|
+
},
|
|
154
|
+
async runCapability(capability) {
|
|
155
|
+
if (!CAPABILITY_IDS.includes(capability)) {
|
|
156
|
+
throw new Error(`unknown capability "${String(capability)}"; known capabilities: ${CAPABILITY_IDS.join(", ")}`);
|
|
157
|
+
}
|
|
158
|
+
// Explicit unavailability: no port call, no fabricated facts, no
|
|
159
|
+
// empty-success degradation.
|
|
160
|
+
return {
|
|
161
|
+
capability,
|
|
162
|
+
state: "unavailable",
|
|
163
|
+
reason: NOT_IMPLEMENTED_REASON,
|
|
164
|
+
plannedLot: CAPABILITY_PLANNED_LOTS[capability],
|
|
165
|
+
delivered: false,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
grammarCoverage(language) {
|
|
169
|
+
// Synchronous coverage uses the pinned manifest; loadGrammar caches the
|
|
170
|
+
// executed result per language so coverage reflects verified assets.
|
|
171
|
+
const loaded = grammarLoadCache.get(language);
|
|
172
|
+
if (loaded?.state === "available") {
|
|
173
|
+
return { axis: "ast", language, state: "current", reason: `packaged grammar verified (sha256 ${loaded.bytesSha256})` };
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
axis: "ast",
|
|
177
|
+
language,
|
|
178
|
+
state: "unavailable",
|
|
179
|
+
reason: `${GRAMMAR_UNAVAILABLE_REASON}: "${language}" has no verified packaged grammar in this session`,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
grammarCoverageReport(language) {
|
|
183
|
+
const coverage = this.grammarCoverage(language);
|
|
184
|
+
return {
|
|
185
|
+
axis: coverage.axis,
|
|
186
|
+
language,
|
|
187
|
+
state: coverage.state,
|
|
188
|
+
reason: coverage.reason,
|
|
189
|
+
checkedAt: ports.clock.now(),
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
async loadGrammar(language) {
|
|
193
|
+
const cached = grammarLoadCache.get(language);
|
|
194
|
+
if (cached)
|
|
195
|
+
return cached;
|
|
196
|
+
const unavailable = (reason) => {
|
|
197
|
+
const result = { language, state: "unavailable", reason };
|
|
198
|
+
grammarLoadCache.set(language, result);
|
|
199
|
+
return result;
|
|
200
|
+
};
|
|
201
|
+
// Reading and validating the manifest is part of the load: an absent,
|
|
202
|
+
// unreadable or invalid manifest yields an explicit unavailable result,
|
|
203
|
+
// never a thrown error and never a silent empty success.
|
|
204
|
+
const manifestOutcome = await loadManifest();
|
|
205
|
+
if (!manifestOutcome.ok || manifestOutcome.manifest === undefined) {
|
|
206
|
+
return unavailable(`${GRAMMAR_UNAVAILABLE_REASON}: ${manifestOutcome.reason ?? "the pinned grammar manifest is unavailable"}`);
|
|
207
|
+
}
|
|
208
|
+
// Clean own-property lookup: inherited names such as "constructor" or
|
|
209
|
+
// "toString" never reach a manifest entry.
|
|
210
|
+
if (!Object.hasOwn(manifestOutcome.manifest.languages, language)) {
|
|
211
|
+
return unavailable(`${GRAMMAR_UNAVAILABLE_REASON}: "${language}" is absent from the pinned grammar manifest`);
|
|
212
|
+
}
|
|
213
|
+
const entry = manifestOutcome.manifest.languages[language];
|
|
214
|
+
let bytes;
|
|
215
|
+
try {
|
|
216
|
+
bytes = await ports.filesystem.readFileBytes(join(grammarDir, entry.file));
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
return unavailable(`${GRAMMAR_UNAVAILABLE_REASON}: the packaged WASM for "${language}" cannot be read through the filesystem port (${errorMessage(error)})`);
|
|
220
|
+
}
|
|
221
|
+
const digest = sha256(bytes);
|
|
222
|
+
if (digest !== entry.sha256) {
|
|
223
|
+
return unavailable(`packaged grammar digest mismatch for "${language}": pinned ${entry.sha256}, read ${digest}`);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const grammar = await ports.grammarRuntime.loadLanguage(bytes);
|
|
227
|
+
const result = {
|
|
228
|
+
language,
|
|
229
|
+
state: "available",
|
|
230
|
+
bytesSha256: digest,
|
|
231
|
+
grammar,
|
|
232
|
+
};
|
|
233
|
+
grammarLoadCache.set(language, result);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
return unavailable(`grammar runtime rejected the packaged WASM for "${language}": ${errorMessage(error)}`);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
validateBatch(batch) {
|
|
241
|
+
return validateFactBatch(batch);
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/** The advertised grammar matrix of this build, from the pinned manifest data. */
|
|
246
|
+
export const ADVERTISED_GRAMMAR_LANGUAGES = ADVERTISED_LANGUAGES;
|
|
247
|
+
// The canonical identity derivation (L6a, spec §3/§12) is part of the public
|
|
248
|
+
// integration surface: h2a and cluster-mesh mint cloneId/worktreeId/
|
|
249
|
+
// declarationId through exactly these functions, so every seat derives
|
|
250
|
+
// byte-identical opaque ids from identical components. The minting needs
|
|
251
|
+
// node:crypto (sha256), which keeps it out of the data-pure /contracts
|
|
252
|
+
// entry; the derivation itself stays pure and deterministic over its
|
|
253
|
+
// arguments (see ./registration.js).
|
|
254
|
+
export { deriveCloneIdentity, deriveWorktreeIdentity, deriveDeclarationIdentity, } from "./registration.js";
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { type GithubRepositoryDto, type PrCoverageDto, type PrObjectJobDto, type PrPageDto, type PrSyncCursorDto, type PrSyncErrorKind, type PullRequestFactDto } from "./contracts.js";
|
|
2
|
+
/** Neutral PR sync extractor version (checkpoints are per version). */
|
|
3
|
+
export declare const PR_SYNC_EXTRACTOR_VERSION = "engram-live/pr-sync/1";
|
|
4
|
+
/**
|
|
5
|
+
* Overlap re-ask on incremental retries (milliseconds): the resume window
|
|
6
|
+
* re-reads recently updated PRs so a page that moved during enumeration is
|
|
7
|
+
* deduplicated instead of lost. Pure constant, consumed by hosts.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PR_SYNC_OVERLAP_MS: number;
|
|
10
|
+
export declare class PrSyncError extends Error {
|
|
11
|
+
readonly kind: PrSyncErrorKind;
|
|
12
|
+
constructor(kind: PrSyncErrorKind, message: string);
|
|
13
|
+
}
|
|
14
|
+
export interface PrPageRequest {
|
|
15
|
+
readonly repository: GithubRepositoryDto;
|
|
16
|
+
readonly cursor: string | null;
|
|
17
|
+
readonly updatedAtSince: number | null;
|
|
18
|
+
/**
|
|
19
|
+
* Conditional validator bound to this exact (repository, filter, cursor)
|
|
20
|
+
* position: the engine replays only the ETag stored for the requested
|
|
21
|
+
* position, never another page's validator. Null means no validator yet.
|
|
22
|
+
*/
|
|
23
|
+
readonly etag: string | null;
|
|
24
|
+
}
|
|
25
|
+
export type PrTransportResult = {
|
|
26
|
+
readonly ok: true;
|
|
27
|
+
readonly page: PrPageDto;
|
|
28
|
+
} | {
|
|
29
|
+
readonly ok: false;
|
|
30
|
+
readonly kind: PrSyncErrorKind;
|
|
31
|
+
readonly message: string;
|
|
32
|
+
readonly retryAfterMs?: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* INJECTED GitHub listing transport (neutral port). Production H: serves it
|
|
36
|
+
* with the approved installed `gh` binary, host-controlled credentials and
|
|
37
|
+
* ETag/rate-limit handling; tests serve fixture pages. No network in engram.
|
|
38
|
+
* The request carries no count limit: the engine never sends a ceiling.
|
|
39
|
+
*/
|
|
40
|
+
export interface GithubPrTransportPort {
|
|
41
|
+
fetchPage(request: PrPageRequest): Promise<PrTransportResult>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* INJECTED durable PR persistence (neutral port, never a base). Engram owns
|
|
45
|
+
* no database; cluster-mesh/app implement it behind this shape (S mapping).
|
|
46
|
+
* Retention contract: saveObservation keeps every distinct observation —
|
|
47
|
+
* a moved head, a state change, a base change or new merge evidence appends
|
|
48
|
+
* a row, never erases the previous one; only byte-identical repeats
|
|
49
|
+
* collapse. Object jobs are keyed by (repository, PR, head OID): recording
|
|
50
|
+
* head B never replaces head A's job, its failures or its receipts.
|
|
51
|
+
*/
|
|
52
|
+
export interface PrSyncPersistencePort {
|
|
53
|
+
loadCursor(repository: GithubRepositoryDto): Promise<PrSyncCursorDto | null>;
|
|
54
|
+
saveCursor(cursor: PrSyncCursorDto): Promise<void>;
|
|
55
|
+
saveObservation(fact: PullRequestFactDto): Promise<void>;
|
|
56
|
+
listObservations(repository: GithubRepositoryDto): Promise<readonly PullRequestFactDto[]>;
|
|
57
|
+
recordObjectJob(job: PrObjectJobDto): Promise<void>;
|
|
58
|
+
updateObjectJob(job: PrObjectJobDto): Promise<void>;
|
|
59
|
+
listObjectJobs(repository: GithubRepositoryDto): Promise<readonly PrObjectJobDto[]>;
|
|
60
|
+
}
|
|
61
|
+
export interface PrSyncInput {
|
|
62
|
+
readonly repository: GithubRepositoryDto;
|
|
63
|
+
readonly sweepKind: "incremental" | "full";
|
|
64
|
+
readonly now: number;
|
|
65
|
+
}
|
|
66
|
+
export interface PrSyncStats {
|
|
67
|
+
readonly pages: number;
|
|
68
|
+
readonly itemsSeen: number;
|
|
69
|
+
readonly unique: number;
|
|
70
|
+
readonly duplicates: number;
|
|
71
|
+
readonly observations: number;
|
|
72
|
+
readonly objectJobsPending: number;
|
|
73
|
+
}
|
|
74
|
+
export interface PrSyncOutcome {
|
|
75
|
+
readonly state: "complete" | "stale";
|
|
76
|
+
readonly observations: readonly PullRequestFactDto[];
|
|
77
|
+
readonly cursor: PrSyncCursorDto;
|
|
78
|
+
readonly coverage: readonly PrCoverageDto[];
|
|
79
|
+
readonly stats: PrSyncStats;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Identity of one persisted observation: the complete observed fact. Two
|
|
83
|
+
* rows sharing one head OID but differing by state, draft flag, refs, base,
|
|
84
|
+
* merge evidence or timestamp are different observations and all persist;
|
|
85
|
+
* only a byte-identical repeat collapses. The (PR, head OID) key is reserved
|
|
86
|
+
* for object-job deduplication (objectJobIdentityKey), never for dropping
|
|
87
|
+
* observations.
|
|
88
|
+
*/
|
|
89
|
+
export declare function observationIdentityKey(fact: PullRequestFactDto): string;
|
|
90
|
+
/**
|
|
91
|
+
* Identity of one object-acquisition job: the PR plus the observed head OID.
|
|
92
|
+
* Two jobs for one PR with different heads are different rows; recording
|
|
93
|
+
* head B never replaces head A's job, failures or receipts.
|
|
94
|
+
*/
|
|
95
|
+
export declare function objectJobIdentityKey(job: Pick<PrObjectJobDto, "repositoryOwner" | "repositoryRepo" | "prNumber" | "headOid">): string;
|
|
96
|
+
/**
|
|
97
|
+
* Deduplicate exact observation repeats by complete observation identity,
|
|
98
|
+
* keeping one row per distinct fact. Observations whose metadata differs
|
|
99
|
+
* (state, base, merge evidence, timestamp, …) are all kept: this is the
|
|
100
|
+
* persistence rule (nothing erased), not the current view.
|
|
101
|
+
*/
|
|
102
|
+
export declare function deduplicatePrObservations(items: readonly PullRequestFactDto[]): readonly PullRequestFactDto[];
|
|
103
|
+
/**
|
|
104
|
+
* Current view over observations: one row per PR identity
|
|
105
|
+
* (owner/repo/number), keeping the newest updatedAt record. Computed
|
|
106
|
+
* separately AFTER every distinct observation is retained, so the live head
|
|
107
|
+
* never erases the observed history.
|
|
108
|
+
*/
|
|
109
|
+
export declare function deduplicatePrPageItems(items: readonly PullRequestFactDto[]): readonly PullRequestFactDto[];
|
|
110
|
+
/**
|
|
111
|
+
* Refuse any row that does not belong to the requested repository: the
|
|
112
|
+
* stored cursor and every received observation must carry the canonical
|
|
113
|
+
* requested identity before any persistence or completion. A foreign row is
|
|
114
|
+
* a protocol failure, never a silent adoption.
|
|
115
|
+
*/
|
|
116
|
+
export declare function assertSameRepository(expected: GithubRepositoryDto, owner: string, repo: string, what: string): void;
|
|
117
|
+
/**
|
|
118
|
+
* ETag position key: one validator per (filter, cursor) position inside the
|
|
119
|
+
* repository pass, so page N's ETag is never replayed for page M.
|
|
120
|
+
*/
|
|
121
|
+
export declare function etagPositionKey(cursor: string | null, updatedAtSince: number | null): string;
|
|
122
|
+
/**
|
|
123
|
+
* Require the explicit end-of-list proof: hasMore false without endProof is
|
|
124
|
+
* a protocol failure, never a silent completion. hasMore true without a
|
|
125
|
+
* continuation cursor is the same failure class.
|
|
126
|
+
*/
|
|
127
|
+
export declare function requireEndOfListProof(page: PrPageDto): void;
|
|
128
|
+
/**
|
|
129
|
+
* S mapping rule: metadata receipts never acknowledge a failed private
|
|
130
|
+
* object acquisition. True only when every job is acquired AND verified:
|
|
131
|
+
* the acquired OID equals the observed head OID, no residual error stands,
|
|
132
|
+
* and known counters agree exactly (a partial count never acknowledges).
|
|
133
|
+
*/
|
|
134
|
+
export declare function canAcknowledgePrObjects(jobs: readonly PrObjectJobDto[]): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Run one neutral PR sync pass: full pagination with overlap/dedup, durable
|
|
137
|
+
* cursor advance only after proven completion, object jobs recorded as
|
|
138
|
+
* pending independently of metadata, failures preserving last success with
|
|
139
|
+
* stale coverage and durable retry state for automatic resume.
|
|
140
|
+
*/
|
|
141
|
+
export declare function runPrSyncPass(input: PrSyncInput, ports: {
|
|
142
|
+
readonly transport: GithubPrTransportPort;
|
|
143
|
+
readonly persistence: PrSyncPersistencePort;
|
|
144
|
+
}): Promise<PrSyncOutcome>;
|