@shardflux/sdk 0.11.0 → 0.12.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/dist/progress.js CHANGED
@@ -1,3 +1,55 @@
1
+ const DURABILITY_STATES = new Set(['pending', 'durable', 'lost']);
2
+ /**
3
+ * The durable copy of a suspend or fork operation (0.12.0+): `result.durability` camelCased, or null when the result has
4
+ * none (the suspend was stored durably before it completed, or another kind).
5
+ */
6
+ export function durabilityOf(op) {
7
+ const d = op.result?.durability;
8
+ if (typeof d !== 'object' || d === null)
9
+ return null;
10
+ const r = d;
11
+ if (typeof r.state !== 'string' || !DURABILITY_STATES.has(r.state))
12
+ return null;
13
+ return {
14
+ state: r.state,
15
+ checkpointId: str(r.checkpoint_id),
16
+ generationId: str(r.generation_id),
17
+ localCommitAt: str(r.local_commit_at),
18
+ durableBy: str(r.durable_by),
19
+ durableAt: str(r.durable_at),
20
+ localCommitToDurableMs: num(r.local_commit_to_durable_ms),
21
+ overdueAt: str(r.overdue_at),
22
+ reason: str(r.reason),
23
+ };
24
+ }
25
+ /** A resume operation's `result.lost_suspend` camelCased (0.12.0+), or null. */
26
+ export function lostSuspendOf(op) {
27
+ const l = op.result?.lost_suspend;
28
+ if (typeof l !== 'object' || l === null)
29
+ return null;
30
+ const r = l;
31
+ const checkpointId = str(r.checkpoint_id);
32
+ if (!checkpointId)
33
+ return null;
34
+ return {
35
+ checkpointId,
36
+ generationId: str(r.generation_id),
37
+ reason: str(r.reason),
38
+ suspendedAt: str(r.suspended_at),
39
+ restoredCheckpointId: str(r.restored_checkpoint_id),
40
+ stateAsOf: str(r.state_as_of),
41
+ };
42
+ }
43
+ /**
44
+ * Whether a suspend or fork operation's capture is in durable storage (0.12.0+): `result.durable`, else `true` for a
45
+ * succeeded suspend or fork whose result predates the field, else null.
46
+ */
47
+ export function isDurable(op) {
48
+ const v = op.result?.durable;
49
+ if (typeof v === 'boolean')
50
+ return v;
51
+ return op.state === 'succeeded' && (op.kind === 'suspend' || op.kind === 'fork') ? true : null;
52
+ }
1
53
  /** Calls every listener; a listener that throws never breaks the SDK call. */
2
54
  export function emitTo(listeners, event) {
3
55
  for (const l of listeners) {
@@ -31,9 +83,9 @@ function diffMs(from, to) {
31
83
  const str = (v) => (typeof v === 'string' && v.length > 0 ? v : null);
32
84
  const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
33
85
  /**
34
- * When a start waiting in `capacity_pending` gives up: the operation's `error.details.deadline_at` (RFC 3339). A start no
35
- * host admits by then fails with `capacity_unavailable` (retryable; nothing was started). Null in any other state, or
36
- * from an API that does not report it.
86
+ * The deadline of a start in `capacity_pending`: the operation's `error.details.deadline_at` (RFC 3339). A start still
87
+ * queued then fails with `capacity_unavailable` (retryable; nothing was started). Null in any other state, or from an
88
+ * API that does not report it.
37
89
  */
38
90
  export function capacityDeadlineOf(op) {
39
91
  if (op.state !== 'capacity_pending')
@@ -66,6 +118,10 @@ export function serverTiming(op) {
66
118
  // Unknown (null) unless the result says so: a result without the field is never read as "not restored".
67
119
  memoryRestored: typeof r.memory_restored === 'boolean' ? r.memory_restored : null,
68
120
  coldBootReason: str(r.cold_boot_reason),
121
+ durable: typeof r.durable === 'boolean' ? r.durable : null,
122
+ suspendPath: str(r.suspend_path),
123
+ durability: durabilityOf(op),
124
+ lostSuspend: lostSuspendOf(op),
69
125
  bootToReadyMs: num(r.boot_to_ready_ms),
70
126
  hostTimingsMs,
71
127
  };
@@ -76,6 +132,8 @@ function outcomeOf(err) {
76
132
  return 'failed';
77
133
  if (name === 'OperationTimeoutError')
78
134
  return 'timed_out';
135
+ if (name === 'DurabilityLostError')
136
+ return 'failed';
79
137
  return 'error';
80
138
  }
81
139
  /** Why a request failed, in one short phrase (for retry records). */
@@ -131,7 +189,7 @@ export class Trace {
131
189
  }
132
190
  /**
133
191
  * Enters a sequential phase (closing the current one). The same phase and reason again is not a new phase.
134
- * `deadlineAt`: when a `capacity_pending` start gives up (added to the event only).
192
+ * `deadlineAt`: the deadline of a `capacity_pending` start (added to the event only).
135
193
  */
136
194
  phase(phase, reason = null, deadlineAt = null) {
137
195
  if (this.#timing)
@@ -218,14 +276,14 @@ export async function traced(trace, fn) {
218
276
  }
219
277
  const fmt = (ms) => (ms === null ? '?' : ms < 1000 ? `${Math.round(ms)} ms` : `${(ms / 1000).toFixed(2)} s`);
220
278
  /**
221
- * A human-readable account of a timing, for logs and bug reports:
279
+ * A human-readable account of a timing, for logs and bug reports. A resume in production:
222
280
  *
223
- * open 34.18 s, succeeded (workspace <id>, operation <id>)
224
- * client: request 20.01 s (held) → capacity_pending 13.52 s (no_ready_host) → running 590 ms → view 42 ms ∥ token 61 ms
225
- * server: queued 33.40 s, ran 620 ms, total 34.02 s; start warm, boot to ready 79 ms
226
- * outside the server: 161 ms
227
- * retries: 1 (POST /v1/workspaces/open, HTTP 503 unavailable, after 200 ms)
281
+ * resume 413 ms, succeeded (workspace <id>, operation <id>)
282
+ * client: request 218 ms → queued 195 ms
283
+ * server: queued 51 ms, ran 290 ms, total 341 ms; resume from local_cache, boot to ready 211 ms, host disk 0 ms, load 6 ms, ready 62 ms, total 211 ms, after_restore 36 ms
284
+ * outside the server: 72 ms
228
285
  *
286
+ * A call that retried a request adds `retries: <n> (<request>, <cause>, after <delay>)`.
229
287
  * A resume that booted the workspace instead of restoring its memory (0.11.0+) says so in the server line:
230
288
  * `resume from cold_boot: processes restarted (runtime_changed)`.
231
289
  */
@@ -249,6 +307,9 @@ export function formatTiming(t) {
249
307
  const how = [
250
308
  s.startPath ? `start ${s.startPath}${s.warmFallback ? ` (warm fallback: ${s.warmFallback})` : ''}` : null,
251
309
  s.resumePath ? `resume from ${s.resumePath}${s.memoryRestored === false ? `: processes restarted${s.coldBootReason ? ` (${s.coldBootReason})` : ''}` : ''}` : null,
310
+ s.lostSuspend ? `restored ${s.lostSuspend.restoredCheckpointId ?? 'no checkpoint'} (latest suspend ${s.lostSuspend.checkpointId} ${s.lostSuspend.reason ?? 'lost'})` : null,
311
+ s.suspendPath === 'local_commit' ? 'sealed on host' : null,
312
+ s.durability?.state === 'durable' ? `durable${s.durability.localCommitToDurableMs !== null ? ` ${fmt(s.durability.localCommitToDurableMs)} later` : ''}` : s.durability ? `durable copy ${s.durability.state}` : null,
252
313
  s.bootToReadyMs !== null ? `boot to ready ${fmt(s.bootToReadyMs)}` : null,
253
314
  s.hostTimingsMs ? `host ${Object.entries(s.hostTimingsMs).map(([k, v]) => `${k} ${fmt(v)}`).join(', ')}` : null,
254
315
  ].filter(Boolean);
package/dist/tar.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A small tar writer for build uploads of folders (contracts §24.2: uncompressed ustar/pax, extracted by the host's
2
+ * A small tar writer for build uploads of folders (uncompressed ustar/pax, extracted by the host's
3
3
  * static tool). Pure: no Node imports, so the browser bundle can carry it.
4
4
  *
5
5
  * The bytes equal CPython's `tarfile.open(mode="w", format=tarfile.PAX_FORMAT)` with every member added by hand (the
package/dist/tar.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A small tar writer for build uploads of folders (contracts §24.2: uncompressed ustar/pax, extracted by the host's
2
+ * A small tar writer for build uploads of folders (uncompressed ustar/pax, extracted by the host's
3
3
  * static tool). Pure: no Node imports, so the browser bundle can carry it.
4
4
  *
5
5
  * The bytes equal CPython's `tarfile.open(mode="w", format=tarfile.PAX_FORMAT)` with every member added by hand (the
@@ -22,7 +22,7 @@ export declare class TemplateFileError extends Error {
22
22
  readonly path: string | undefined;
23
23
  constructor(message: string, path?: string);
24
24
  }
25
- /** Largest single upload (contracts §24.2: 5 GiB, one presigned PUT). */
25
+ /** Largest single upload (5 GiB, one presigned PUT). */
26
26
  export declare const UPLOAD_BYTES_MAX = 5368709120;
27
27
  /** Most entries a folder tar may have (the host's extraction limit, §24.2). */
28
28
  export declare const TAR_ENTRIES_MAX = 200000;
@@ -33,7 +33,7 @@ export class TemplateFileError extends Error {
33
33
  this.path = path;
34
34
  }
35
35
  }
36
- /** Largest single upload (contracts §24.2: 5 GiB, one presigned PUT). */
36
+ /** Largest single upload (5 GiB, one presigned PUT). */
37
37
  export const UPLOAD_BYTES_MAX = 5_368_709_120;
38
38
  /** Most entries a folder tar may have (the host's extraction limit, §24.2). */
39
39
  export const TAR_ENTRIES_MAX = 200_000;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Template registry, custom template builds (recipe v1 Dockerfiles and recipe v2, contracts §24.1), build uploads
2
+ * Template registry, custom template builds (recipe v1 Dockerfiles and recipe v2), build uploads
3
3
  * (§24.2), recipe export, version test instances, package search, the version file tree and diff, and template dev
4
4
  * mode (drafts and test instances) over the application API (/v1). Registry and build types are written by hand and
5
5
  * checked against the generated contract in type-checks.ts; the Templates v2 and template editor types alias the
@@ -15,7 +15,7 @@ import type { YamlParser } from './template-file.js';
15
15
  import type { ToolName } from './tokens.js';
16
16
  import { Workspace } from './workspace.js';
17
17
  type S = components['schemas'];
18
- /** Recipe v2 (contracts §24.1): the `recipe` of a build, the export's `recipe` and the document of template.yaml. */
18
+ /** Recipe v2: the `recipe` of a build, the export's `recipe` and the document of template.yaml. */
19
19
  export type TemplateRecipeV2 = S['TemplateRecipeV2'];
20
20
  /** One `build.files[]` entry: an upload (`upload: "sha256:<hex>"`), or in template.yaml a local path (`from`). */
21
21
  export type TemplateRecipeV2File = NonNullable<TemplateRecipeV2['build']['files']>[number];
@@ -40,7 +40,7 @@ export type TemplateVersionRecipe = S['TemplateVersionRecipe'];
40
40
  export type TemplatePackage = S['TemplatePackage'];
41
41
  export type TemplatePackagePage = S['TemplatePackagePage'];
42
42
  export type TemplatePackageEcosystem = 'apt' | 'pip' | 'npm';
43
- /** The languages a base offers a recipe v2 (contracts §24.6 `GET …/template-languages`): the language table for its platform base. */
43
+ /** The languages a base offers a recipe v2 (`GET …/template-languages`): the language table for its platform base. */
44
44
  export type TemplateLanguages = S['TemplateLanguages'];
45
45
  export type TemplateLanguage = TemplateLanguages['data'][number];
46
46
  export type CreateVersionTestInstanceBody = S['CreateVersionTestInstanceBody'];
@@ -52,18 +52,18 @@ export type TemplateBuildRecipeV2 = Extract<NonNullable<S['TemplateBuild']['reci
52
52
  }>;
53
53
  /** Start commands and services of a workspace's version (§24.4); null when it has none. */
54
54
  export type WorkspaceStartup = S['WorkspaceStartup'];
55
- /** Manifest v2 `defaults` of a version (contracts §19.7). */
55
+ /** Manifest v2 `defaults` of a version. */
56
56
  export type TemplateDefaults = S['TemplateDefaults'];
57
57
  /** How a version was produced: a recipe build, a saved workspace (or draft publish), or git (reserved). */
58
58
  export type TemplateSource = S['TemplateSource'];
59
59
  /** The version's file list state (the tree and diff routes read it). */
60
60
  export type TemplateFilesSummary = S['TemplateFilesSummary'];
61
- /** Storage of a template version (contracts §19.13). */
61
+ /** Storage of a template version. */
62
62
  export type TemplateStorage = S['TemplateStorage'];
63
63
  export type TemplateStorageWarning = S['TemplateStorageWarning'];
64
64
  /** Template storage of an organization (counts toward retained_state_gib). */
65
65
  export type OrgTemplateStorage = S['OrgTemplateStorage'];
66
- /** One inode path of a template version (contracts §19.10). */
66
+ /** One inode path of a template version. */
67
67
  export type TemplateFileEntry = S['TemplateFileEntry'];
68
68
  /** One directory level of a version's tree (keyset-paginated). */
69
69
  export type TemplateFilePage = S['TemplateFilePage'];
@@ -72,7 +72,7 @@ export type TemplateDiffEntry = S['TemplateDiffEntry'];
72
72
  export type TemplateDiffChange = TemplateDiffEntry['change'];
73
73
  /** One page of a version diff; the first page (no cursor) carries `summary`. */
74
74
  export type TemplateDiffPage = S['TemplateDiffPage'];
75
- /** The live draft of an organization template (contracts §19.9). */
75
+ /** The live draft of an organization template. */
76
76
  export type TemplateDraft = S['TemplateDraft'];
77
77
  /** A disk-only capture of the draft (a test instance or a publish starts from one). */
78
78
  export type DraftState = S['DraftState'];
@@ -164,7 +164,7 @@ export interface TemplateVersion {
164
164
  rootfs_sha256: string | null;
165
165
  } | null;
166
166
  defaults: TemplateDefaults;
167
- /** What a workspace of this version gets when it opens (0.7.0; contracts §24.3). `defaults` equals `settings.defaults`. */
167
+ /** What a workspace of this version gets when it opens (0.7.0). `defaults` equals `settings.defaults`. */
168
168
  settings: TemplateSettings;
169
169
  /** The file list the tree and diff read (`loaded` when files() and diff() can answer). */
170
170
  files: TemplateFilesSummary;
@@ -199,7 +199,7 @@ export interface TemplateSummary {
199
199
  created_at: string;
200
200
  archived_at: string | null;
201
201
  shadowed_by_organization_template: boolean;
202
- /** Mutable template-level defaults (contracts §20.5, §20.7): the size of a start without caps.memory_mib, and the idle policy of a workspace that sets none. */
202
+ /** Mutable template-level defaults: the size of a start without caps.memory_mib, and the idle policy of a workspace that sets none. */
203
203
  defaults: {
204
204
  memory_mib: number | null;
205
205
  idle_policy: string | null;
@@ -347,7 +347,7 @@ export interface TemplateBuild {
347
347
  };
348
348
  publishable: boolean;
349
349
  builder_availability: BuilderAvailability;
350
- /** `recipe` (Dockerfile dialect) or `workspace` (save-as-template, draft publish; contracts §19.8). */
350
+ /** `recipe` (Dockerfile dialect) or `workspace` (save-as-template, draft publish). */
351
351
  source_kind: 'recipe' | 'workspace';
352
352
  source_workspace_id: string | null;
353
353
  /** The captured checkpoint (null until the capture operation succeeded). */
@@ -385,7 +385,7 @@ export interface CreateTemplateBuildParams {
385
385
  displayName?: string;
386
386
  /**
387
387
  * Recipe v1 (a Dockerfile) or recipe v2 (0.7.0; `schema: "shardflux.template-recipe.v2"`: languages, packages,
388
- * uploaded files, steps, auto network and settings; contracts §24.1). Recipe v2 files reference uploads
388
+ * uploaded files, steps, auto network and settings). Recipe v2 files reference uploads
389
389
  * (`templates.uploads.put()`); `buildFromFile()` / `buildFromRecipe()` upload local `from` paths for you.
390
390
  */
391
391
  recipe: TemplateRecipe | TemplateRecipeV2;
@@ -411,7 +411,7 @@ export interface WaitForBuildOptions {
411
411
  /** Backoff used when the server does not hold the poll (default 1 s doubling to 10 s). */
412
412
  pollIntervalMs?: number;
413
413
  maxPollIntervalMs?: number;
414
- /** Ask the server to hold each poll until the build changes (`Prefer: wait`, contracts §3; default true). */
414
+ /** Ask the server to hold each poll until the build changes (`Prefer: wait`; default true). */
415
415
  serverWait?: boolean;
416
416
  signal?: AbortSignal;
417
417
  /** Called with each build view the wait observes whose state or registration changed (0.7.0), the settled one included. */
@@ -429,7 +429,7 @@ export interface SaveAsTemplateResponse {
429
429
  operation: Operation | null;
430
430
  build: TemplateBuild;
431
431
  }
432
- /** Save-as-template (contracts §19.8): everything in the workspace, minus the sf-scrub.v1 list, becomes one new org layer. */
432
+ /** Save-as-template: everything in the workspace, minus the sf-scrub.v1 list, becomes one new org layer. */
433
433
  export interface SaveAsTemplateParams {
434
434
  /** Organization template to save into (created when absent; platform slugs are refused with platform_template_slug). */
435
435
  templateSlug: string;
@@ -440,7 +440,7 @@ export interface SaveAsTemplateParams {
440
440
  /** Defaults of the new version (default: the source version's, else persistent). */
441
441
  defaults?: TemplateDefaultsInput;
442
442
  /**
443
- * Settings of the new version (0.7.0; contracts §24.3): each field given replaces that field of the source version's
443
+ * Settings of the new version (0.7.0): each field given replaces that field of the source version's
444
444
  * settings, each field left out is carried forward. `settings.defaults` together with `defaults` is 422 invalid_settings.
445
445
  */
446
446
  settings?: TemplateSettingsInput;
@@ -536,7 +536,7 @@ export interface DraftOpened {
536
536
  operation: Operation | null;
537
537
  }
538
538
  /**
539
- * Template dev mode for one organization template (contracts §19.9): the single live draft (a layered, persistent
539
+ * Template dev mode for one organization template: the single live draft (a layered, persistent
540
540
  * workspace on the draft base), its captured states, disposable test instances (session workspaces on a copy of a
541
541
  * state) and publishing the draft as the next version. Mutations need build access (owners/admins, API keys with a
542
542
  * tool permission); others get 403 forbidden with details.reason template_dev_mode_role.
@@ -654,7 +654,7 @@ export declare class TemplateUploadError extends Error {
654
654
  constructor(sha256: string, status: number, code: string | null, detail?: string);
655
655
  }
656
656
  /**
657
- * Build uploads (contracts §24.2): files and folders a recipe v2 copies into the template, stored once per
657
+ * Build uploads: files and folders a recipe v2 copies into the template, stored once per
658
658
  * organization and content (SHA-256). Needs build access (API keys with a tool permission). Uploads count toward the
659
659
  * organization's template storage while they exist; one nothing references is deleted 7 days later.
660
660
  */
@@ -795,7 +795,7 @@ export interface CreateVersionTestInstanceParams {
795
795
  idempotencyKey?: string;
796
796
  }
797
797
  /**
798
- * Test instances of a version (contracts §24.6): a session workspace on a registered version of the organization's
798
+ * Test instances of a version: a session workspace on a registered version of the organization's
799
799
  * template, published or not, so a build can be tried before it is published. Owners, admins and API keys with a
800
800
  * tool permission (403 template_dev_mode_role otherwise).
801
801
  */
@@ -805,7 +805,7 @@ export declare class TemplateVersionTestInstancesApi {
805
805
  /** Opens one (202; waits until ready unless `wait: false`). It ends with close() or when idle. */
806
806
  create(slug: string, version: number, params?: CreateVersionTestInstanceParams): Promise<Workspace>;
807
807
  }
808
- /** Package names for the editor's pickers (contracts §24.6): apt (a base's index), pip (names only) and npm. */
808
+ /** Package names for the editor's pickers: apt (a base's index), pip (names only) and npm. */
809
809
  export declare class TemplatePackagesApi {
810
810
  #private;
811
811
  constructor(ctx: () => ClientContext);
@@ -848,7 +848,7 @@ export declare class TemplatesApi {
848
848
  organizationId?: string;
849
849
  }): Promise<TemplateLanguages>;
850
850
  /**
851
- * Node only: builds a template from template.yaml (or a .json file with the same document; contracts §24.1). The
851
+ * Node only: builds a template from template.yaml (or a .json file with the same document). The
852
852
  * file is recipe v2; each `build.files[]` entry may name a local `from` path (relative to the file): folders are
853
853
  * packed as the reproducible tar, files are sent as they are, both uploaded unless the organization already has
854
854
  * them, and the build is created (and awaited with `wait`). YAML needs the optional `yaml` package or `parseYaml`.
@@ -885,7 +885,7 @@ export declare class TemplatesApi {
885
885
  owner?: TemplateOwner;
886
886
  }): Promise<TemplateDetail | null>;
887
887
  /**
888
- * One directory level of a version's file tree (contracts §19.10), sorted by name bytes. 409 conflict with
888
+ * One directory level of a version's file tree, sorted by name bytes. 409 conflict with
889
889
  * details.reason file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found or
890
890
  * version_not_found; 422 invalid_path. File contents are not served: open a draft or test instance for that.
891
891
  */
@@ -901,7 +901,7 @@ export declare class TemplatesApi {
901
901
  diff(slug: string, params: TemplateDiffParams): Promise<TemplateDiffPage>;
902
902
  /** Every diff entry, following next_cursor. */
903
903
  diffAll(slug: string, params: Omit<TemplateDiffParams, 'cursor'>): AsyncGenerator<TemplateDiffEntry>;
904
- /** Dev mode of one organization template: its draft, states, test instances and publish (contracts §19.9). */
904
+ /** Dev mode of one organization template: its draft, states, test instances and publish. */
905
905
  draft(slug: string, params?: {
906
906
  organizationId?: string;
907
907
  }): TemplateDraftApi;
package/dist/templates.js CHANGED
@@ -53,7 +53,7 @@ async function openedWorkspace(ctx, res, p) {
53
53
  return { workspace: new Workspace(ctx, view, wrap), operation };
54
54
  }
55
55
  /**
56
- * Template dev mode for one organization template (contracts §19.9): the single live draft (a layered, persistent
56
+ * Template dev mode for one organization template: the single live draft (a layered, persistent
57
57
  * workspace on the draft base), its captured states, disposable test instances (session workspaces on a copy of a
58
58
  * state) and publishing the draft as the next version. Mutations need build access (owners/admins, API keys with a
59
59
  * tool permission); others get 403 forbidden with details.reason template_dev_mode_role.
@@ -369,7 +369,7 @@ async function sendUpload(ctx, meta, body, organizationId, signal) {
369
369
  return { upload: again.body.upload, ref, uploaded: true };
370
370
  }
371
371
  /**
372
- * Build uploads (contracts §24.2): files and folders a recipe v2 copies into the template, stored once per
372
+ * Build uploads: files and folders a recipe v2 copies into the template, stored once per
373
373
  * organization and content (SHA-256). Needs build access (API keys with a tool permission). Uploads count toward the
374
374
  * organization's template storage while they exist; one nothing references is deleted 7 days later.
375
375
  */
@@ -458,7 +458,7 @@ function uploadLocal(ctx, src, organizationId, signal) {
458
458
  return sendUpload(ctx, { sha256: src.sha256, size: src.size, kind: src.kind }, { open: () => fileBody(src.uploadPath), replayable: true }, organizationId, signal);
459
459
  }
460
460
  const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
461
- // ---- recipe export, version test instances, package search (contracts §24.6) --------------------------------------------
461
+ // ---- recipe export, version test instances, package search --------------------------------------------
462
462
  export class TemplateVersionsApi {
463
463
  #ctx;
464
464
  constructor(ctx) {
@@ -475,7 +475,7 @@ export class TemplateVersionsApi {
475
475
  }
476
476
  }
477
477
  /**
478
- * Test instances of a version (contracts §24.6): a session workspace on a registered version of the organization's
478
+ * Test instances of a version: a session workspace on a registered version of the organization's
479
479
  * template, published or not, so a build can be tried before it is published. Owners, admins and API keys with a
480
480
  * tool permission (403 template_dev_mode_role otherwise).
481
481
  */
@@ -504,7 +504,7 @@ export class TemplateVersionTestInstancesApi {
504
504
  return (await openedWorkspace(ctx, res, params)).workspace;
505
505
  }
506
506
  }
507
- /** Package names for the editor's pickers (contracts §24.6): apt (a base's index), pip (names only) and npm. */
507
+ /** Package names for the editor's pickers: apt (a base's index), pip (names only) and npm. */
508
508
  export class TemplatePackagesApi {
509
509
  #ctx;
510
510
  constructor(ctx) {
@@ -574,7 +574,7 @@ export class TemplatesApi {
574
574
  return this.#organization;
575
575
  }
576
576
  /**
577
- * Node only: builds a template from template.yaml (or a .json file with the same document; contracts §24.1). The
577
+ * Node only: builds a template from template.yaml (or a .json file with the same document). The
578
578
  * file is recipe v2; each `build.files[]` entry may name a local `from` path (relative to the file): folders are
579
579
  * packed as the reproducible tar, files are sent as they are, both uploaded unless the organization already has
580
580
  * them, and the build is created (and awaited with `wait`). YAML needs the optional `yaml` package or `parseYaml`.
@@ -705,7 +705,7 @@ export class TemplatesApi {
705
705
  }
706
706
  }
707
707
  /**
708
- * One directory level of a version's file tree (contracts §19.10), sorted by name bytes. 409 conflict with
708
+ * One directory level of a version's file tree, sorted by name bytes. 409 conflict with
709
709
  * details.reason file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found or
710
710
  * version_not_found; 422 invalid_path. File contents are not served: open a draft or test instance for that.
711
711
  */
@@ -741,7 +741,7 @@ export class TemplatesApi {
741
741
  cursor = page.next_cursor ?? undefined;
742
742
  } while (cursor !== undefined);
743
743
  }
744
- /** Dev mode of one organization template: its draft, states, test instances and publish (contracts §19.9). */
744
+ /** Dev mode of one organization template: its draft, states, test instances and publish. */
745
745
  draft(slug, params = {}) {
746
746
  return new TemplateDraftApi(this.#ctx, slug, params.organizationId);
747
747
  }
package/dist/tools.d.ts CHANGED
@@ -49,7 +49,7 @@ export declare function validateArgs(schema: JsonSchema, value: unknown, path?:
49
49
  export interface WorkspaceToolsOptions {
50
50
  /**
51
51
  * Tool permissions to expose (default: the tools of the workspace's last token, else all). A file-first workspace
52
- * (`workspace.mode`, contracts §29) gets only the `exec` and `files` tools: `exec` runs each command as an execution
52
+ * (`workspace.mode`) gets only the `exec` and `files` tools: `exec` runs each command as an execution
53
53
  * (a fresh VM on the workspace's files; the result adds `execution_id`, `state`, `changed` and `tree_revision`), and
54
54
  * the process, terminal, git and browser tools are not offered because nothing runs between executions.
55
55
  */
@@ -70,7 +70,7 @@ export interface WorkspaceToolsOptions {
70
70
  * Send `workspace.hint()` when a tool call starts, without waiting for it (default true; 0.9.0+), so a parked
71
71
  * workspace is being restored while the call is prepared. Pass false when you call `workspace.hint()` yourself
72
72
  * earlier (e.g. when the model starts streaming a tool call). `read_file`, `list_files` and `search_files` never send
73
- * it: a sleeping workspace serves them from its disk without waking (contracts §26.4), and the hint would wake a
73
+ * it: a sleeping workspace serves them from its disk without waking, and the hint would wake a
74
74
  * suspended workspace (or restore a hibernated one) that the read does not need.
75
75
  */
76
76
  hint?: boolean;
@@ -81,7 +81,7 @@ export interface WorkspaceToolsOptions {
81
81
  mode?: WorkspaceMode;
82
82
  /**
83
83
  * File-first workspaces: called with the execution id before the `exec` tool sends its execution (0.9.0+). An
84
- * execution cannot be canceled, so a caller that stops waiting (an aborted signal) can still fetch its result later
84
+ * execution runs to completion, so a caller that stops waiting (an aborted signal) can still fetch its result later
85
85
  * with `workspace.executions.get(id)`.
86
86
  */
87
87
  onExecution?: (executionId: string) => void;
package/dist/tools.js CHANGED
@@ -83,11 +83,11 @@ export function validateArgs(schema, value, path = '$') {
83
83
  return issues;
84
84
  }
85
85
  const ALL = ['exec', 'files', 'pty', 'process', 'git', 'browser'];
86
- /** The tool permissions whose tools work on a file-first workspace (contracts §29.8): files and executions. */
86
+ /** The tool permissions whose tools work on a file-first workspace: files and executions. */
87
87
  const FILE_FIRST_TOOLS = ['exec', 'files'];
88
88
  /** Changed paths returned to the model per execution (the rest is flagged `changed_truncated`). */
89
89
  const MAX_CHANGED_LISTED = 200;
90
- /** Tools served from a sleeping workspace's disk without waking it (contracts §26.4): the runner sends no hint. */
90
+ /** Tools served from a sleeping workspace's disk without waking it: the runner sends no hint. */
91
91
  const DISK_READS = new Set(['read_file', 'list_files', 'search_files']);
92
92
  const obj = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
93
93
  const path = (description = 'Absolute path inside the workspace, e.g. /home/user/project/main.py') => ({ type: 'string', minLength: 1, maxLength: 4096, description });
@@ -107,7 +107,7 @@ export function workspaceTools(workspace, opts = {}) {
107
107
  });
108
108
  const max = opts.maxOutputBytes ?? 65_536;
109
109
  const prefix = opts.prefix ?? '';
110
- // A file-first workspace (contracts §29) has files and executions only: no processes, terminals, version control or
110
+ // A file-first workspace has files and executions only: no processes, terminals, version control or
111
111
  // browser between calls, so those tools are not offered, and exec runs each command as an execution.
112
112
  const fileFirst = (opts.mode ?? workspace.mode) === 'file_first';
113
113
  const allowed = new Set((opts.tools ?? workspace.grantedTools ?? ALL).filter((t) => !fileFirst || FILE_FIRST_TOOLS.includes(t)));
@@ -436,7 +436,7 @@ export function workspaceTools(workspace, opts = {}) {
436
436
  const issues = validateArgs(d.parameters, args);
437
437
  if (issues.length > 0)
438
438
  throw new ToolArgumentError(`${prefix}${d.name}`, issues);
439
- // Fire and forget: a parked workspace starts restoring while this call is prepared (contracts §26.6). Not for
439
+ // Fire and forget: a parked workspace starts restoring while this call is prepared. Not for
440
440
  // the reads a sleeping workspace serves from its disk (§26.4): the hint would wake it for nothing.
441
441
  if (opts.hint !== false && !DISK_READS.has(d.name)) {
442
442
  workspace
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Client version check (contracts §30.4; 0.9.0+). `GET /v1/client-versions` (unauthenticated) lists every published
2
+ * Client version check (0.9.0+). `GET /v1/client-versions` (unauthenticated) lists every published
3
3
  * client with its `latest` and `minimum_supported` version.
4
4
  *
5
5
  * const s = await checkClientVersion(); // @shardflux/sdk at SDK_VERSION
package/dist/volumes.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shared volumes over the application API (/v1, contracts §15). Types come from
2
+ * Shared volumes over the application API (/v1). Types come from
3
3
  * the generated OpenAPI document (schemas `Volume` and `VolumeAttachment`).
4
4
  *
5
5
  * A volume is persistent shared storage (EFS-backed) owned by a project. It is