Skip to content

Commit e99da36

Browse files
committed
refactor: extract durable capture kit
1 parent 9f00d63 commit e99da36

106 files changed

Lines changed: 1852 additions & 1166 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/adr/0019-request-bound-platform-runtime.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ process primitives outside the shared host-command port. R11 applies these rules
9292
dynamic, and re-export edges; package-owned tests may import their own public façade. Contracts may
9393
depend on kernel vocabulary but never on concrete platform packages or daemon implementation types.
9494

95+
Durable-capture mechanics shared by more than one implementation live in the private
96+
`@agent-device/capture-kit` workspace package, with the enforced direction
97+
`kernel < contracts < capture-kit < platform/provider/daemon`. Contracts retains pure vocabulary and
98+
plan models; process supervision, live-handle implementations, recovery helpers, runtime codecs, and
99+
capture parsers do not live there. `capture-kit` is a domain package for durable capture, not a generic
100+
platform-common package, and it preserves the package façades' implementation-lazy loading boundary.
101+
Its introduction carries the normal workspace-package compliance surface: `check:affected`
102+
selection, R11/R13 package enumeration, and the composite typecheck project list.
103+
95104
Canonical family, `AppleOS`, public-leaf, and selector identity remain declared in
96105
`@agent-device/kernel/device`. Platform-module metadata references one canonical family; during
97106
coexistence the legacy plugin registry derives its family identity from the same declaration rather
@@ -330,11 +339,14 @@ but reattachment never scans telemetry to rebuild state.
330339

331340
Every authoritative home exposes a deterministic facet-owned lookup or enumeration path after
332341
process loss. Its neutral record carries session/device identity, the exact runtime-owner reference,
333-
descriptor and metadata, an ownership/fence token, and a lifecycle state sufficient to distinguish
334-
starting, active, completing, completed, and cleanup-pending recovery. A new handle is not exposed
335-
until the persisted ownership fence is acquired. Every finish/cleanup attempt holds that ownership
336-
guard through destructive work and the persisted transition, or delegates to an operation that
337-
atomically enforces the token, so a prior owner cannot later terminate a transferred resource.
342+
descriptor and metadata, an ownership/fence token, and one of two persisted lifecycle states:
343+
`open` or `completed`. In-progress distinctions such as starting, active, completing, and
344+
cleanup-pending are phase metadata on the open record, not additional lifecycle states. The fence and
345+
the descriptor remain authoritative across every open phase; cleanup uncertainty therefore cannot be
346+
encoded as a terminal lifecycle. A new handle is not exposed until the persisted ownership fence is
347+
acquired. Every finish/cleanup attempt holds that ownership guard through destructive work and the
348+
persisted transition, or delegates to an operation that atomically enforces the token, so a prior
349+
owner cannot later terminate a transferred resource.
338350

339351
Persisting a descriptor does not make external-resource start and descriptor write atomic. A
340352
platform whose native tool cannot close that crash window retains a platform-owned orphan marker or

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
"check:affected:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/platform-packages.test.ts scripts/check-affected/run.test.ts",
131131
"check:coverage-changed": "node --experimental-strip-types scripts/coverage-changed/run.ts",
132132
"check:coverage-changed:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/coverage-changed/model.test.ts scripts/coverage-changed/run.test.ts",
133-
"check:layering": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/layering/model.test.ts scripts/layering/zone-policy.test.ts scripts/layering/daemon-modularity.test.ts scripts/layering/package-boundaries.test.ts scripts/layering/platform-package-policy.test.ts scripts/layering/platform-package-repository.test.ts scripts/layering/platform-package-source-policy.test.ts scripts/layering/device-inventory-cutover-policy.test.ts scripts/layering/logs-runtime-cutover-policy.test.ts scripts/layering/facade-exports.test.ts scripts/layering/bin-alias-fast-path.test.ts && node --experimental-strip-types scripts/layering/check.ts",
133+
"check:layering": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/layering/model.test.ts scripts/layering/zone-policy.test.ts scripts/layering/daemon-modularity.test.ts scripts/layering/package-boundaries.test.ts scripts/layering/platform-package-policy.test.ts scripts/layering/platform-package-repository.test.ts scripts/layering/platform-package-source-policy.test.ts scripts/layering/device-inventory-cutover-policy.test.ts scripts/layering/logs-runtime-cutover-policy.test.ts scripts/layering/contracts-implementation-policy.test.ts scripts/layering/facade-exports.test.ts scripts/layering/bin-alias-fast-path.test.ts && node --experimental-strip-types scripts/layering/check.ts",
134134
"depgraph": "node --experimental-strip-types scripts/depgraph/build.ts",
135135
"depgraph:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/depgraph/model.test.ts scripts/depgraph/affected.test.ts",
136136
"check:production-exports": "fallow dead-code --config fallow-production-exports.json --production --unused-exports --fail-on-issues",
@@ -149,7 +149,7 @@
149149
"check:unit": "pnpm check:contention-retry && pnpm test:unit && pnpm check:tmpdir-leaks && pnpm test:smoke",
150150
"check": "pnpm check:tooling && pnpm check:fallow && pnpm check:unit",
151151
"prepack": "pnpm check:mcp-metadata && pnpm package:npm",
152-
"typecheck": "tsc -b packages/xml packages/kernel packages/contracts packages/platform-apple packages/platform-android packages/platform-harmonyos packages/platform-vega packages/platform-linux packages/platform-web packages/ad-script packages/selectors packages/ad-replay packages/maestro packages/replay-test packages/provider-webdriver packages/provider-limrun && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
152+
"typecheck": "tsc -b packages/xml packages/kernel packages/contracts packages/capture-kit packages/platform-apple packages/platform-android packages/platform-harmonyos packages/platform-vega packages/platform-linux packages/platform-web packages/ad-script packages/selectors packages/ad-replay packages/maestro packages/replay-test packages/provider-webdriver packages/provider-limrun && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
153153
"test-app:install": "pnpm install --dir examples/test-app",
154154
"test-app:start": "pnpm --dir examples/test-app start",
155155
"test-app:ios": "pnpm --dir examples/test-app ios",
@@ -252,6 +252,7 @@
252252
"yauzl": "^3.4.0"
253253
},
254254
"devDependencies": {
255+
"@agent-device/capture-kit": "workspace:*",
255256
"@agent-device/ad-replay": "workspace:*",
256257
"@agent-device/ad-script": "workspace:*",
257258
"@agent-device/contracts": "workspace:*",

packages/capture-kit/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@agent-device/capture-kit",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"description": "Private durable-capture mechanics shared by platform runtimes, providers, and daemon orchestration.",
7+
"dependencies": {
8+
"@agent-device/contracts": "workspace:*",
9+
"@agent-device/kernel": "workspace:*"
10+
},
11+
"exports": {
12+
".": {
13+
"types": "./src/index.ts",
14+
"default": "./src/index.ts"
15+
}
16+
}
17+
}

packages/contracts/src/app-log-live-handle-core.ts renamed to packages/capture-kit/src/app-log-live-handle.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { AppError } from '@agent-device/kernel/errors';
2-
import type {
3-
AppLogCompletion,
4-
AppLogLiveHandle,
5-
AppLogLiveHandleImplementation,
6-
} from './app-log-runtime.ts';
7-
import { isConfirmedCleanup, type CleanupOutcome, type FinishOutcome } from './durable-resource.ts';
2+
import {
3+
isConfirmedCleanup,
4+
type AppLogCompletion,
5+
type AppLogLiveHandle,
6+
type AppLogLiveSnapshot,
7+
type CleanupOutcome,
8+
type FinishOutcome,
9+
} from '@agent-device/contracts/platform';
10+
11+
type AppLogLiveHandleImplementation = Readonly<{
12+
inspect(): AppLogLiveSnapshot;
13+
finish(): Promise<FinishOutcome<AppLogCompletion>>;
14+
forceCleanup(): Promise<CleanupOutcome>;
15+
}>;
816

917
/** Internal idempotent adapter underlying the narrower public handle factories. */
1018
export function createAppLogLiveHandle(
@@ -25,6 +33,25 @@ export function createAppLogLiveHandle(
2533
});
2634
}
2735

36+
/** Derives forced cleanup from an idempotent finish transaction. */
37+
export function createAppLogLiveHandleFromFinish(
38+
implementation: Readonly<{
39+
inspect(): AppLogLiveSnapshot;
40+
finish(): Promise<FinishOutcome<AppLogCompletion>>;
41+
}>,
42+
): AppLogLiveHandle {
43+
return createAppLogLiveHandle({
44+
inspect: implementation.inspect,
45+
finish: implementation.finish,
46+
forceCleanup: async () => {
47+
const outcome = await implementation.finish();
48+
return outcome.status === 'completed'
49+
? { status: 'cleaned' }
50+
: { status: 'cleanup-pending', reason: outcome.reason, message: outcome.message };
51+
},
52+
});
53+
}
54+
2855
function assertConfirmedCleanup(outcome: CleanupOutcome): void {
2956
if (isConfirmedCleanup(outcome)) return;
3057
throw new AppError(

packages/contracts/src/app-log-pid-process.test.ts renamed to packages/capture-kit/src/app-log-pid-process.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {
33
AppLogBackgroundProcess,
44
AppLogOutputSink,
55
AppLogRuntimeHost,
6-
} from './app-log-runtime.ts';
6+
} from '@agent-device/contracts/platform';
77
import { createPidScopedAppLogProcess } from './app-log-pid-process.ts';
88

99
describe('PID-scoped app-log process lifecycle', () => {

packages/contracts/src/app-log-pid-process.ts renamed to packages/capture-kit/src/app-log-pid-process.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { LogBackend } from './logs.ts';
1+
import type { LogBackend } from '@agent-device/contracts/observability';
22
import type {
33
AppLogBackgroundProcess,
44
AppLogLiveHandle,
55
AppLogLiveSnapshot,
66
AppLogProcessStart,
77
AppLogProcessCommand,
88
AppLogRuntimeHost,
9-
} from './app-log-runtime.ts';
9+
} from '@agent-device/contracts/platform';
1010
import { createAppLogLiveHandleFromFinish } from './app-log-live-handle.ts';
1111

1212
export type PidScopedAppLogProcessOptions = Readonly<{

packages/contracts/src/app-log-pid-runtime.test.ts renamed to packages/capture-kit/src/app-log-pid-runtime.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import type { DeviceInfo } from '@agent-device/kernel/device';
22
import { expect, test, vi } from 'vitest';
3+
import {
4+
type AppLogBackgroundProcess,
5+
type AppLogRuntimeHost,
6+
type DurableDescriptorCodec,
7+
} from '@agent-device/contracts/platform';
38
import {
49
createDurableResourceEnvelope,
510
encodeDurableDescriptor,
611
} from './durable-resource-envelope.ts';
7-
import type { AppLogBackgroundProcess, AppLogRuntimeHost } from './app-log-runtime.ts';
812
import { createPidScopedAppLogRuntimeOwner } from './app-log-pid-runtime.ts';
9-
import type { DurableDescriptorCodec } from './durable-resource-envelope.ts';
1013

1114
type TestDescriptor = Readonly<{
1215
transport: 'test-local' | 'test-provider';
@@ -74,7 +77,7 @@ test('PID-scoped runtime owner derives canonical artifacts and publishes one com
7477
},
7578
owner: selectedOwner,
7679
fence: input.fence,
77-
lifecycle: 'active',
80+
lifecycle: 'open',
7881
descriptor: encodeDurableDescriptor(descriptorCodec, descriptor),
7982
}),
8083
});

packages/contracts/src/app-log-pid-runtime.ts renamed to packages/capture-kit/src/app-log-pid-runtime.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
import type { DeviceInfo, Platform } from '@agent-device/kernel/device';
22
import { AppError } from '@agent-device/kernel/errors';
3-
import type { LogBackend } from './logs.ts';
4-
import type { HostCommandRequest } from './platform-runtime-host.ts';
53
import type {
6-
DeviceBinding,
7-
DeviceRuntimeOwner,
8-
RuntimeFacts,
9-
RuntimeOwnerRef,
10-
} from './platform-runtime.ts';
11-
import { localRuntimeOwner, sameRuntimeOwner } from './platform-runtime.ts';
12-
import type {
13-
AppLogDescriptorCodec,
14-
AppLogDoctorResult,
154
AppLogProcessCommand,
165
AppLogProcessTransport,
176
AppLogRuntimeHost,
187
AppLogRuntimeOperations,
198
AppLogSessionArtifacts,
209
AppLogStartInput,
2110
AppLogStartResult,
22-
} from './app-log-runtime.ts';
11+
DeviceBinding,
12+
DeviceRuntimeOwner,
13+
DurableDescriptorCodec,
14+
DurableResourceEnvelope,
15+
HostCommandRequest,
16+
RuntimeFacts,
17+
RuntimeOwnerRef,
18+
} from '@agent-device/contracts/platform';
19+
import { localRuntimeOwner, sameRuntimeOwner } from '@agent-device/contracts/platform';
20+
import type { LogBackend } from '@agent-device/contracts/observability';
2321
import { createAppLogRecoveryOperations, createAppLogStartResult } from './app-log-runtime.ts';
24-
import type { DurableResourceEnvelope } from './durable-resource-envelope.ts';
2522
import {
2623
cleanupManagedAppLogProcess,
2724
reattachCleanupOnlyAppLogProcess,
@@ -69,13 +66,13 @@ export type PidScopedAppLogRuntimeOptions<
6966
family: Family;
7067
backend: LogBackend;
7168
label: string;
72-
codec: AppLogDescriptorCodec<Descriptor>;
69+
codec: DurableDescriptorCodec<Descriptor, 'app-log'>;
7370
startUnavailableHint: string;
7471
cleanupFailureMessage: string;
7572
doctor(
7673
context: PidScopedAppLogRuntimeContext,
7774
appBundleId: string | undefined,
78-
): Promise<AppLogDoctorResult>;
75+
): ReturnType<AppLogRuntimeOperations['appLogDoctor']>;
7976
validateStart?(context: PidScopedAppLogStartContext): void;
8077
process(context: PidScopedAppLogStartContext): Promise<PidScopedAppLogProcessPlan>;
8178
descriptor(input: {

packages/contracts/src/app-log-process-recovery.test.ts renamed to packages/capture-kit/src/app-log-process-recovery.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import assert from 'node:assert/strict';
22
import { test, vi } from 'vitest';
3-
import type { AppLogProcessMarkerReadOutcome, AppLogProcessOwnership } from './app-log-runtime.ts';
3+
import type {
4+
AppLogProcessMarkerReadOutcome,
5+
AppLogProcessOwnership,
6+
} from '@agent-device/contracts/platform';
47
import {
58
cleanupManagedAppLogProcess,
69
reattachCleanupOnlyAppLogProcess,

0 commit comments

Comments
 (0)