mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
05a1d76f2e
* test: gate daemon RPC wire compatibility against the last released tag (#1432) ADR 0006 fixes exactly when DAEMON_RPC_PROTOCOL_VERSION must be bumped, and nothing checked that it was. The runtime guard (readRemoteDaemonHealth) refuses a mismatched peer, but only fires when someone remembered the bump — a wire change that skipped it left both sides advertising protocol 2 while parsing different payloads, which is the failure ADR 0006 exists to prevent. Local daemons cannot skew (isReusableDaemonInfo takes over on any package version mismatch). Cross-machine is skewed by design — proxy, cloud/limrun, a remote macOS host — and ADR 0006 explicitly rules package version out as the compatibility gate there, so the one boundary where skew is intended was the one boundary with no gate. test/wire-compat/surface.ts declares the wire surface grouped by the ADR bullet each group serves, quoting it, with an `uncovered` note where a bullet is only partly digestible (the /health and /rpc literals inside http-server.ts stay reviewer-owned: a moved route 404s at connect time rather than misparsing). ledger.json records what each declaration hashes to, at which protocol version. Two gates, split for the same reason the replay-compat corpus splits: - unit-core holds the ledger to its source and prints the digest to paste; - Released-Surface Compatibility reads the ledger at the last RELEASED tag and requires the drift since then to carry a bump or a compatibleChanges ack. From one commit a bumped ledger and an unbumped one are both just an edited file, so only a released baseline can tell them apart. Acks are keyed by the digest they cover, so one "added an optional field" cannot launder later changes. Digests ignore comments and formatting; the manifest's closure is derived from the AST, so a field typed by an unlisted sibling fails rather than sitting outside the gate. CI cost: one added job (checkout + toolchain + two node scripts, ~1 min), mirroring the existing full-history replay-compat job. * test: close wire-surface overclaim and make the closure fail closed (#1432) Addresses both review P1s on #1717. P1 — the manifest materially overclaimed ADR 0006 coverage. It quoted all four bullets while digesting only the payload TYPES, so the producer and consumer seams could break a skewed peer without moving a listed digest. Now listed on both sides of every boundary: JSON-RPC method sets and the projections that turn each method's params into a DaemonRequest, createRpcError/sendJson/ writeRpcResponseEnvelope, resolveToken and the auth-hook types, upload preflight/finalize/308 handlers and the resumable ticket shape, artifact route and download/inventory framing, REST error mapping, and the client's own payload builder, lease-method mapping, response parser and error projection. 57 -> 117 declarations. What stays out is now named rather than implied: createDaemonHttpServer's dispatch wiring and the /health and /rpc literals inside it. Everything it dispatches WITH is digested individually, and a moved route 404s at connect time rather than misparsing — the loud failure, not the silent one. P1 — imported and re-exported payload shapes escaped the closure. declarationHomes() scanned only the manifest's own files and the walk continued silently when a name could not be placed, so a listed type could gain foo?: ImportedShape from a new module and stay green. Resolution is now explicit and fails closed: relative imports, workspace specifiers (through the owning package's own exports map, so a re-pointed export cannot drop a type), and facade re-export chains. Every referenced name must land on a listed declaration, a waiver with a written reason, a declared external module, or the TS/Node global set. Fixed two extractor blind spots the walk exposed: a declaration's own generic parameters and `as const` were being reported as references. Planted-red proofs (wire-mutations.test.ts): 13 cases independently mutate method naming, response serialization, response parsing, auth projection, upload ticket shape, 308 framing, artifact framing, REST error mapping, and progress framing, each asserting the digest moves; 3 probes prove the closure really reaches across a package boundary, a facade re-export, and a plain relative import. Mutations apply inside the declaration's own span — a whole-file replace silently hit a sibling sharing the substring, which is how the first draft of one case passed vacuously. The largest waiver pair (InternalRequestOptions, CommandFlags) rests on ADR 0006's own additive rule: they reach the peer inside DaemonRequest's untyped flags/input bags, and the decision says a new flag needs no bump. Digesting them would fire the gate on every new CLI flag and train reviewers to rubber-stamp acks. * test: list the consumer half of the auxiliary HTTP boundaries (#1432) Addresses the remaining review P1 on #1717. The manifest claimed both sides of response/upload/artifact framing while listing nothing from upload-client.ts, daemon-artifacts.ts, or the health consumer in daemon-client-transport.ts, so those parsers could narrow without moving a listed digest or protocol 2. Now listed (117 -> 141 declarations): - /health consumer: RemoteDaemonHealth, readHealthPayload, readDaemonHttpHealth, readRemoteDaemonHealth. This is the sharpest of the three — narrowing the reader or the comparison disables the very refusal ADR 0006 exists to guarantee, and nothing else in the repo would notice. - /upload consumer: UploadResponse, UploadPreflightResponse, UploadPreflightResult, parseUploadPreflightResult, requestUploadPreflight, uploadDirectArtifact, tryDirectUploadWithResume, shouldRetryDirectUpload, finalizeDirectUpload, uploadLegacyArtifact, ARTIFACT_HASH_ALGORITHM, isStringRecord, and PreparedUploadArtifact — whose sha256/sizeBytes/fileName/artifactType/ contentType fields ARE the preflight body the daemon parses. - /artifacts/* consumer: DaemonArtifactEndpoint, buildDaemonArtifactUrl, isRemoteDaemon, DownloadRemoteArtifactParams, downloadRemoteArtifact, materializeRemoteArtifacts, resolveMaterializedArtifactPath. Running the closure fail-closed over the new files surfaced three more stops, each decided rather than skipped: PreparedUploadArtifact listed (it is payload), UploadProgressSink waived (client-local rendering, never leaves the process), and src/daemon/types.ts#DaemonArtifact waived as a re-export alias of the listed kernel type, matching its DaemonRequest/DaemonResponse siblings. 10 more planted-red mutations cover the new seams: health version-read and mismatch-refusal defeated, RemoteDaemonHealth field dropped, preflight parser narrowed, preflight/legacy response shapes narrowed, finalize body key renamed, ticket field renamed, artifact tenant header dropped, artifact URL moved. A fourth closure probe proves the upload-consumer files are genuinely reached by the walk rather than merely listed. 22 -> 33 tests. The README now states the coverage as a producer/consumer table per boundary, so the claim is checkable at a glance instead of asserted in prose. * test: list the client half of the resumable 308 contract (#1432) Addresses the third review P1 on #1717. Listing the daemon's handleResumableUpload proved it still PRODUCES 308; nothing proved the client still CONSUMES the released one. src/remote/upload-stream.ts owns that half and was entirely outside the manifest, so a newer client could stop accepting `upload-offset`, change how it reads `Range: bytes=0-N`, or emit a different resumed `Content-Range` without moving one of the 141 listed digests. Now listed (141 -> 151): UploadStreamResponse, streamFileToHttpRequest, streamFileToHttpRequestAttempt, buildUploadRequestHeaders, isUploadResumeStatus, isUploadRedirectStatus, parseUploadResumeOffset, parseNonNegativeIntegerHeader, firstHeaderValue, MAX_UPLOAD_REDIRECTS. streamFileToHttpRequestAttempt is listed despite its size, unlike createDaemonHttpServer which stays in `uncovered`. The distinction is stated at the declaration: the HTTP server only dispatches to handlers that are each digested, while the attempt loop IS the resume state machine — it decides whether a 308 continues the upload and what the next request carries, so its sequencing alone can break a released daemon while every helper keeps its digest. 6 new planted-red mutations prove the client half moves the ledger: a dropped `upload-offset` fallback, narrowed Range parsing, a changed resumed Content-Range, 308 no longer treated as continue, a narrowed UploadStreamResponse, and dropped header-value coercion. 33 -> 39 tests. Closure fail-closed surfaced two more stops: UploadStreamProgressOptions waived (local byte-progress rendering) and URL/URLSearchParams added to the global set. README now carries a `/upload` resume row in the producer/consumer table, and names the pattern behind three rounds of review: the coverage sentence kept getting written ahead of the coverage, so the table and the `uncovered` notes are the claims to trust — they are checkable against surface.ts, prose is not. --------- Co-authored-by: Claude <noreply@anthropic.com>
362 lines
14 KiB
TypeScript
362 lines
14 KiB
TypeScript
/**
|
|
* The daemon RPC wire surface, grouped by the ADR 0006 rule each group serves
|
|
* (#1432).
|
|
*
|
|
* ADR 0006 says exactly when `DAEMON_RPC_PROTOCOL_VERSION` must be bumped, and
|
|
* until this manifest existed nothing checked that it was. The runtime guard
|
|
* (`readRemoteDaemonHealth`) refuses a mismatched peer — but only when someone
|
|
* remembered to bump the constant, so a wire change that skipped the bump left
|
|
* both sides advertising the same protocol while parsing different payloads.
|
|
*
|
|
* The grouping is not decoration: each group quotes the ADR bullet it covers,
|
|
* so a reader can check the manifest against the decision rather than against
|
|
* someone's summary of it. `uncovered` is the honest half — where a bullet is
|
|
* only partly digestible, the group says which part is reviewer-owned and why,
|
|
* instead of implying coverage the digests do not provide.
|
|
*
|
|
* Review P1 (2026-08-10) corrected a real overclaim here: the first version
|
|
* quoted all four bullets but digested only the payload TYPES, leaving the
|
|
* producer and consumer seams — method sets, response serialization, auth
|
|
* projection, upload ticket/308 framing, artifact framing, and the client's
|
|
* own parsers — able to break a skewed peer without moving a listed digest.
|
|
* Both sides of every boundary are now listed, and what remains outside is
|
|
* named in `uncovered` rather than implied to be covered.
|
|
*/
|
|
|
|
export type WireDeclarationRef = {
|
|
/** Repo-relative source file. */
|
|
file: string;
|
|
/** Top-level declaration name, exported or not. */
|
|
name: string;
|
|
};
|
|
|
|
export type WireSurfaceGroup = {
|
|
/** The ADR 0006 "bump it for" bullet this group covers, quoted. */
|
|
adrBullet: string;
|
|
declarations: readonly WireDeclarationRef[];
|
|
/** Part of the bullet the digests deliberately do not cover, and why. */
|
|
uncovered?: string;
|
|
};
|
|
|
|
const KERNEL_CONTRACTS = 'packages/kernel/src/contracts.ts';
|
|
const KERNEL_ERRORS = 'packages/kernel/src/errors.ts';
|
|
const KERNEL_DEVICE = 'packages/kernel/src/device.ts';
|
|
const REQUEST_PROGRESS = 'packages/contracts/src/request-progress.ts';
|
|
const HTTP_CONTRACT = 'src/daemon/http-contract.ts';
|
|
const HTTP_HEALTH = 'src/daemon/http-health.ts';
|
|
const HTTP_ERRORS = 'src/daemon/http-errors.ts';
|
|
const HTTP_SERVER = 'src/daemon/server/http-server.ts';
|
|
const UPLOAD_HTTP = 'src/daemon/upload-http.ts';
|
|
const ARTIFACT_HTTP = 'src/daemon/downloadable-artifact-http.ts';
|
|
const PROGRESS_PROTOCOL = 'src/daemon/request-progress-protocol.ts';
|
|
const CLIENT_RPC = 'src/daemon/client/daemon-client-rpc.ts';
|
|
const CLIENT_PROGRESS = 'src/daemon/client/daemon-client-progress.ts';
|
|
const CLIENT_TRANSPORT = 'src/daemon/client/daemon-client-transport.ts';
|
|
const UPLOAD_CLIENT = 'src/remote/upload-client.ts';
|
|
const REMOTE_ARTIFACTS = 'src/remote/daemon-artifacts.ts';
|
|
const UPLOAD_STREAM = 'src/remote/upload-stream.ts';
|
|
|
|
function from(file: string, ...names: string[]): WireDeclarationRef[] {
|
|
return names.map((name) => ({ file, name }));
|
|
}
|
|
|
|
export const WIRE_SURFACE: readonly WireSurfaceGroup[] = [
|
|
{
|
|
adrBullet: 'HTTP route requirements for /health, /rpc, /upload, or /artifacts/*.',
|
|
declarations: [
|
|
...from(
|
|
HTTP_CONTRACT,
|
|
'DAEMON_HTTP_BASE_PATH',
|
|
'buildDaemonHttpUrl',
|
|
'buildDaemonHttpBaseUrl',
|
|
),
|
|
...from(HTTP_HEALTH, 'DaemonHealthPayload', 'buildDaemonHealthPayload'),
|
|
// A shrunk body limit rejects payloads a released client still sends, so
|
|
// it is a route requirement rather than an implementation detail.
|
|
...from(HTTP_SERVER, 'MAX_HTTP_RPC_BODY_BYTES'),
|
|
...from(
|
|
UPLOAD_HTTP,
|
|
'DIRECT_UPLOAD_PATH_PREFIX',
|
|
'UploadHttpRoute',
|
|
'resolveUploadHttpRoute',
|
|
'handleUpload',
|
|
'handleUploadPreflight',
|
|
// Carries the 308 resumable-upload framing (status, headers).
|
|
'handleResumableUpload',
|
|
'handleUploadFinalize',
|
|
'resolveHttpRequestBaseUrl',
|
|
),
|
|
...from(
|
|
ARTIFACT_HTTP,
|
|
'DownloadableArtifactHttpRoute',
|
|
'resolveDownloadableArtifactHttpRoute',
|
|
'readArtifactId',
|
|
'readRequestPathname',
|
|
),
|
|
// Consumer side of /health: the client reads this payload and refuses a
|
|
// mismatched peer from it, so a narrowed reader defeats the very check
|
|
// ADR 0006 built. `readRemoteDaemonHealth` is where the comparison lives.
|
|
...from(
|
|
CLIENT_TRANSPORT,
|
|
'RemoteDaemonHealth',
|
|
'readHealthPayload',
|
|
'readDaemonHttpHealth',
|
|
'readRemoteDaemonHealth',
|
|
),
|
|
],
|
|
// What is left is `createDaemonHttpServer`, a 200+ line dispatcher whose
|
|
// body churns for reasons that are not protocol changes. Everything it
|
|
// dispatches WITH — method sets, envelope, error framing, auth projection,
|
|
// request projections — is digested individually above and below, so the
|
|
// uncovered remainder is the wiring plus the two path literals it compares
|
|
// (`/health`, `/rpc`). Those stay reviewer-owned because their failure mode
|
|
// is the loud one: a moved route answers 404 at connect time, before any
|
|
// payload is exchanged. Everything digested here can misparse silently
|
|
// instead, which is what the gate exists to prevent.
|
|
uncovered:
|
|
'createDaemonHttpServer dispatch wiring, and the /health and /rpc path literals inside it: ' +
|
|
'a moved route 404s at connect time rather than misparsing, so it stays reviewer-owned.',
|
|
},
|
|
{
|
|
adrBullet: 'Authentication semantics required to authorize RPC, upload, or artifact requests.',
|
|
declarations: [
|
|
...from(
|
|
HTTP_CONTRACT,
|
|
'buildDaemonHttpAuthHeaders',
|
|
'DAEMON_HTTP_TENANT_HEADER',
|
|
'buildDaemonHttpTenantHeaders',
|
|
),
|
|
...from(
|
|
HTTP_SERVER,
|
|
'HttpAuthHookContext',
|
|
'HttpAuthHookResult',
|
|
'HttpAuthHook',
|
|
'HttpAuthDecision',
|
|
'resolveToken',
|
|
'readHeaderValue',
|
|
'enforceDaemonToken',
|
|
'authorizeAuxiliaryHttpRequest',
|
|
),
|
|
...from(UPLOAD_HTTP, 'AuxiliaryHttpAuthorizer', 'buildUploadTicketAuthHeaders'),
|
|
...from(ARTIFACT_HTTP, 'DownloadableArtifactHttpAuthorizer'),
|
|
],
|
|
},
|
|
{
|
|
adrBullet:
|
|
'JSON-RPC envelope shape, method naming, request id handling, or command request projection.',
|
|
declarations: [
|
|
...from(
|
|
KERNEL_CONTRACTS,
|
|
'JsonRpcId',
|
|
'JsonRpcRequestEnvelope',
|
|
'jsonRpcRequestSchema',
|
|
'CommandRpcParams',
|
|
'commandRpcParamsSchema',
|
|
'DaemonRequest',
|
|
'DaemonRequestMeta',
|
|
'SessionRuntimeHints',
|
|
'daemonRuntimeSchema',
|
|
'DaemonInstallSource',
|
|
'DAEMON_LOCK_POLICIES',
|
|
'DaemonLockPolicy',
|
|
'LEASE_BACKENDS',
|
|
'LeaseBackend',
|
|
'SESSION_ISOLATION_MODES',
|
|
'SessionIsolationMode',
|
|
'RESPONSE_LEVELS',
|
|
'ResponseLevel',
|
|
),
|
|
...from(KERNEL_DEVICE, 'PLATFORM_SELECTORS', 'PlatformSelector'),
|
|
// The CLI's projection of a command into the request the daemon receives.
|
|
// Its embedded flag/option vocabulary is waived rather than listed — see
|
|
// closure-policy.ts: those reach the peer inside DaemonRequest's untyped
|
|
// `flags`/`input` bags, and ADR 0006 calls new flags additive.
|
|
...from('src/commands/cli-grammar/types.ts', 'DaemonCommandRequest'),
|
|
// The lease method vocabulary the client and daemon must agree on.
|
|
...from('src/core/lease-scope.ts', 'LeaseRpcCommand'),
|
|
// Producer side: the method vocabulary a released client sends, and the
|
|
// projections that turn each method's params into a DaemonRequest.
|
|
...from(
|
|
HTTP_SERVER,
|
|
'JsonRpcRequest',
|
|
'JsonRpcResponse',
|
|
'COMMAND_RPC_METHODS',
|
|
'INSTALL_FROM_SOURCE_RPC_METHODS',
|
|
'RELEASE_MATERIALIZED_PATHS_RPC_METHODS',
|
|
'LEASE_RPC_METHOD_TO_COMMAND',
|
|
'SUPPORTED_RPC_METHODS',
|
|
'isCommandRpcMethod',
|
|
'methodToDaemonRequest',
|
|
'parseCommandRpcParams',
|
|
'toDaemonRequest',
|
|
'toLeaseDaemonRequest',
|
|
'toInstallFromSourceDaemonRequest',
|
|
'toReleaseMaterializedPathsDaemonRequest',
|
|
),
|
|
// Consumer side: the payload the client actually puts on the wire. A
|
|
// client-only change here breaks an older daemon just as surely.
|
|
...from(
|
|
CLIENT_RPC,
|
|
'buildHttpRpcPayload',
|
|
'isLeaseRpcCommand',
|
|
'leaseRpcMethodForCommand',
|
|
'buildLeaseRpcParams',
|
|
),
|
|
],
|
|
},
|
|
{
|
|
adrBullet:
|
|
'Response, error, artifact, upload, or progress-stream framing that existing clients parse.',
|
|
declarations: [
|
|
...from(
|
|
KERNEL_CONTRACTS,
|
|
'DaemonResponse',
|
|
'DaemonResponseData',
|
|
'ResponseCost',
|
|
'DaemonArtifact',
|
|
'DaemonArtifactType',
|
|
'DaemonArtifactKnownType',
|
|
),
|
|
...from(KERNEL_ERRORS, 'DaemonError'),
|
|
// "These are wire values" — the progress module says so itself: the daemon
|
|
// serializes them onto the response stream and the CLI reconstructs them.
|
|
...from(
|
|
REQUEST_PROGRESS,
|
|
'RequestProgressEvent',
|
|
'ReplayTestSuiteProgressEvent',
|
|
'ReplayTestProgressEvent',
|
|
'CommandProgressEvent',
|
|
),
|
|
...from(
|
|
PROGRESS_PROTOCOL,
|
|
'DaemonProgressEnvelope',
|
|
'DaemonResponseEnvelope',
|
|
'shouldStreamRequestProgress',
|
|
'isDaemonProgressEnvelope',
|
|
'isDaemonResponseEnvelope',
|
|
'serializeDaemonProgressEnvelope',
|
|
'serializeDaemonResponseEnvelope',
|
|
'serializeDaemonRpcResponseEnvelope',
|
|
),
|
|
...from(
|
|
HTTP_SERVER,
|
|
'createRpcError',
|
|
'sendJson',
|
|
'writeProgressEnvelope',
|
|
'writeRpcResponseEnvelope',
|
|
'jsonRpcCodeForNormalizedError',
|
|
),
|
|
...from(
|
|
HTTP_ERRORS,
|
|
'NormalizedHttpError',
|
|
'statusCodeForNormalizedError',
|
|
'sendRestJsonError',
|
|
),
|
|
...from(
|
|
UPLOAD_HTTP,
|
|
'UploadPreflightBody',
|
|
'UploadFinalizeBody',
|
|
'readUploadPreflightBody',
|
|
'readUploadFinalizeBody',
|
|
'sendJson',
|
|
'sendUploadedArtifactResponse',
|
|
),
|
|
// The resumable-upload ticket the preflight response hands back.
|
|
...from('src/daemon/resumable-upload.ts', 'BeginResumableUploadOptions'),
|
|
// `NormalizedHttpError` is `ReturnType<typeof normalizeError>`, so the
|
|
// function and its return type — not a type alias — are what fix the
|
|
// REST error payload a released client parses.
|
|
...from(KERNEL_ERRORS, 'normalizeError', 'NormalizedError'),
|
|
...from(ARTIFACT_HTTP, 'handleArtifactInventory', 'handleArtifactDownload'),
|
|
// Consumer side: what the client accepts back. A parser narrowed here
|
|
// rejects a released daemon's response without any server change.
|
|
...from(
|
|
CLIENT_RPC,
|
|
'handleDaemonHttpResponseBody',
|
|
'parseDaemonHttpResponseBody',
|
|
'toDaemonHttpRpcError',
|
|
'resolveDaemonHttpResult',
|
|
),
|
|
...from(
|
|
CLIENT_PROGRESS,
|
|
'ProgressResponseFormat',
|
|
'shouldReadDaemonProgressStream',
|
|
'createInvalidDaemonResponseError',
|
|
),
|
|
// Consumer side of the auxiliary /upload boundary: the shapes the client
|
|
// expects back from preflight, direct/resumable, legacy and finalize, and
|
|
// the parser that decides whether a daemon's preflight is usable at all.
|
|
...from(
|
|
UPLOAD_CLIENT,
|
|
'ARTIFACT_HASH_ALGORITHM',
|
|
'UploadResponse',
|
|
'UploadPreflightResponse',
|
|
'UploadPreflightResult',
|
|
'parseUploadPreflightResult',
|
|
'isStringRecord',
|
|
'requestUploadPreflight',
|
|
'uploadDirectArtifact',
|
|
'tryDirectUploadWithResume',
|
|
'shouldRetryDirectUpload',
|
|
'finalizeDirectUpload',
|
|
'uploadLegacyArtifact',
|
|
),
|
|
// The prepared artifact whose fields (sha256, sizeBytes, fileName,
|
|
// artifactType, contentType) ARE the preflight body the daemon parses.
|
|
...from('src/remote/upload-client-artifact.ts', 'PreparedUploadArtifact'),
|
|
// Consumer side of the resumable 308 contract. Listing the daemon's
|
|
// `handleResumableUpload` proves it still PRODUCES 308; it says nothing
|
|
// about the client still CONSUMING the released one. These own which
|
|
// offset headers are accepted (`x-upload-offset`, `upload-offset`,
|
|
// `Range: bytes=0-N`) and what `Content-Range` a resumed PUT emits.
|
|
//
|
|
// `streamFileToHttpRequestAttempt` is listed despite its size, unlike
|
|
// `createDaemonHttpServer` above: that one only dispatches to handlers
|
|
// that are each digested, while this IS the resume state machine — it
|
|
// decides whether a 308 continues the upload and what the next request
|
|
// carries, so a change to its sequencing alone can break a released
|
|
// daemon while every helper below keeps its digest.
|
|
...from(
|
|
UPLOAD_STREAM,
|
|
'MAX_UPLOAD_REDIRECTS',
|
|
'UploadStreamResponse',
|
|
'streamFileToHttpRequest',
|
|
'streamFileToHttpRequestAttempt',
|
|
'buildUploadRequestHeaders',
|
|
'isUploadRedirectStatus',
|
|
'isUploadResumeStatus',
|
|
'parseUploadResumeOffset',
|
|
'parseNonNegativeIntegerHeader',
|
|
'firstHeaderValue',
|
|
),
|
|
// Consumer side of /artifacts/*: URL construction, the inventory/header/
|
|
// body materialization the client performs, and the artifact fields it
|
|
// rewrites on the way through.
|
|
...from(
|
|
REMOTE_ARTIFACTS,
|
|
'DaemonArtifactEndpoint',
|
|
'buildDaemonArtifactUrl',
|
|
'isRemoteDaemon',
|
|
'DownloadRemoteArtifactParams',
|
|
'downloadRemoteArtifact',
|
|
'materializeRemoteArtifacts',
|
|
'resolveMaterializedArtifactPath',
|
|
),
|
|
],
|
|
},
|
|
];
|
|
|
|
/** Stable ledger key for a declaration: `<file>#<name>`. */
|
|
export function wireDeclarationKey(ref: WireDeclarationRef): string {
|
|
return `${ref.file}#${ref.name}`;
|
|
}
|
|
|
|
export const WIRE_DECLARATIONS: readonly WireDeclarationRef[] = WIRE_SURFACE.flatMap(
|
|
(group) => group.declarations,
|
|
);
|
|
|
|
/** Files the manifest draws declarations from. */
|
|
export const WIRE_SURFACE_FILES: readonly string[] = [
|
|
...new Set(WIRE_DECLARATIONS.map((ref) => ref.file)),
|
|
].sort();
|