diff --git a/.agents/architect.md b/.agents/architect.md index f54ca77f0..45a1f240b 100644 --- a/.agents/architect.md +++ b/.agents/architect.md @@ -248,7 +248,7 @@ requirements. A finding has a **structural consequence** only when it changes one or more of: - what is authorized to execute; -- which durable identity or retained history is accepted; +- which durable identity or saved history is accepted; - what durable state is committed, published, or journaled; - whether replay can resume the intended run; - ownership of a transaction, resource, invocation, or lifecycle; diff --git a/.agents/issue-writing.md b/.agents/issue-writing.md index 01ac3b75a..fc3e7559a 100644 --- a/.agents/issue-writing.md +++ b/.agents/issue-writing.md @@ -116,7 +116,7 @@ not a summary of the implementation plan. only when both are accurate. If one reading promises different behavior, make the object or relationship explicit. 16. **Use observable language for durable concepts.** Prefer “run and resume” or - another consequence over “retained” when storage is not necessary to + another consequence over “saved” when storage is not necessary to distinguish the work. 17. **Use code formatting for literal product names.** Commands, components, props, and extensions use their authored spelling. Ordinary concepts remain @@ -176,7 +176,7 @@ evidence rather than the transcript of how the design was discovered. the contract. Say **a** for an ordinary instance and **one** only when exact cardinality matters. 8. **Describe workflow behavior through actions.** Prefer start, interrupt, - resume, fork, export, and **workflow run** over the adjective **retained** + resume, fork, export, and **workflow run** over the adjective **saved** when persistence is only an implementation consequence. Use retain or preserve when storage itself is the contract. 9. **Define the relevant alternative.** A qualifier such as in-process, @@ -317,7 +317,7 @@ Present exactly five things: or wording judgment, or ask for approval when none remains. Do not move to the next issue until the reader answers. Apply an accepted change -immediately and verify the exact title, body, and labels GitHub retained. Apply +immediately and verify the exact title, body, and labels GitHub saved. Apply an accepted Quest classification to both the `Quest:` prefix and `quest` label. Keep a visible count of completed entries. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20546f855..cfe48bac4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -228,7 +228,7 @@ jobs: - name: The Component the compiled binary carries run: deno test --allow-all --frozen scripts/tests/plan-component-compiled.test.ts - # The entry host a REPL entry's file work is retained by is assembled by + # The entry host a REPL entry's file work is saved by is assembled by # `compiled.ts`, which is a different entrypoint from the one the source # row runs. A binary built without it starts a REPL, performs the work and # retains none of it, so the defect would appear at a person's first @@ -258,7 +258,7 @@ jobs: # Fetch adapter, and detaches the response before binding it. All three # live in the module graph, so only the binary shows they survived # `deno compile`. The script owns the server the request reaches. - - name: Smoke test a retained HTTP read with the compiled binary + - name: Smoke test a saved HTTP read with the compiled binary run: deno run --allow-all --frozen scripts/smoke-fetch.ts # A built-in resolves from the module graph rather than a search path. diff --git a/AGENTS.md b/AGENTS.md index c900139bd..059c4dcd2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -666,7 +666,7 @@ An architecture finding has a **structural consequence** only when it changes one or more of: - what is authorized to execute; -- which durable identity or retained history is accepted; +- which durable identity or saved history is accepted; - what durable state is committed, published, or journaled; - whether replay can resume the intended run; - ownership of a transaction, resource, invocation, or lifecycle; diff --git a/architecture.md b/architecture.md index c3252021b..8a4f8a8d7 100644 --- a/architecture.md +++ b/architecture.md @@ -3,7 +3,7 @@ ## Reading this architecture Read this document to understand the rules every engine implementation preserves. -Start with a document running, then follow its execution, failures and retained +Start with a document running, then follow its execution, failures and saved state. The detailed boundaries explain what an implementation may do; the terminology and construct inventory are references after those ideas are clear. @@ -42,7 +42,7 @@ A workflow instead retains the definition bytes, props, journal and filesystem state under a run ID. Partial replay rebuilds the live resources needed to continue and restores completed effects from their records, then performs the first unrecorded work. It does not write or delete again merely because those steps are reached again. -A completed root returns its retained output and result without expanding the +A completed root returns its saved output and result without expanding the document or attaching operational providers. The journal is historical; the Workspace is the current filesystem frontier. @@ -186,12 +186,12 @@ inspected or standard input is read. A `-` consumed as another option's value is not a document argument; another command's `-` keeps that command's meaning, and `--eval -` keeps its refusal. -Core represents the supplied source as `retainedSource("", source)`. +Core represents the supplied source as `savedSource("", source)`. What comes back is admitted through the existing supplied-source protocol rather than a form of its own. The origin `` and the exact text travel together, which is what source positions, diagnostics, the root binding and the durable -root import all carry — so replay restores the retained source without reading +root import all carry — so replay restores the saved source without reading anything, and two different programs are two different roots without a second identity field. `` is an origin and never a path: no file of that name is read or created, and every relative operation still resolves from the contextual @@ -241,7 +241,7 @@ a caller asked for — two callers may spell one request differently — and it never substitutes for the exact target: it does not occupy the recorded exact-target field, it never enters a workflow definition, and it is never re-resolved against a newer checkout to decide what a resumed run means. A glob -is retained in exactly one place, a failed selection's structural record, and +is saved in exactly one place, a failed selection's structural record, and only so that an ordinary failed execution can be reproduced. The command line is the first consumer of that split. `xmd run` resolves a @@ -250,7 +250,7 @@ that resolved — so a file replaced between the two reads fails on the target t run chose, rather than silently running whatever the glob would name now. `xmd workflow start` is the second, and it is stricter because its answer is -retained. It resolves the selector once, against the exact bytes it is about to +saved. It resolves the selector once, against the exact bytes it is about to retain, and stores only what core answered. A resume re-enters that section without resolving anything again: the glob is gone, and the bytes it was resolved against are the run's. @@ -272,8 +272,8 @@ Validating that identity is **execution-owned**. Replay guards are composable policy — a handler installed further out may decline to delegate — so identity, which must not be negotiable, is not decided there. The execution owns the journal it replays through and validates the recorded selection inside the read -itself, ahead of public guard policy, of a retained terminal result being -reused, of authored work, and of any append. The retained history it owns covers +itself, ahead of public guard policy, of a saved terminal result being +reused, of authored work, and of any append. The saved history it owns covers every event that takes part in that decision, a recorded completion included, so no event can present one identity to the validation and another to the run. @@ -325,7 +325,7 @@ durable is written. value>` binds by reference; a construct that evaluates an arbitrary operand keeps its own contract. - **Absence is not durable state.** Prop resolution records nothing of its own. - No resolved prop, validated prop, retained event or replay-restored prop holds + No resolved prop, validated prop, saved event or replay-restored prop holds `undefined`, and a replay reconstructs the same validated prop set through ordinary expansion. @@ -367,7 +367,7 @@ middleware. Selecting a document target does not disturb any of this. Projection retains the original source ranges and scans each one under its own origin, so a -retained element keeps the offset and line it was authored at, and with them +saved element keeps the offset and line it was authored at, and with them its expansion ID. Two runs of the same document under different targets therefore agree on the ID of every element they both retain, and may share IDs without sharing effects — run identity and workflow-definition identity are @@ -428,7 +428,7 @@ way out. Selection is not a permission and not durable state: it appends no journal event, a partial replay reconstructs it while completed effects replay, and a completed -document replay reuses the retained result without expanding the body. `` +document replay reuses the saved result without expanding the body. `` does not end the body, so later documentation runs and a later failure, cancellation or teardown failure keeps its existing precedence. @@ -467,7 +467,7 @@ The selected `` contributes its own expansion frame even though the one identity. Neither construct is durable state: they append no journal event, a partial replay rebuilds the selection through ordinary expansion while completed effects in the chosen branch replay from their own records, and a completed -document replay reuses the retained result without expanding anything. +document replay reuses the saved result without expanding anything. ### Concurrent spawned children @@ -506,7 +506,7 @@ colliding, and work after `` continues on the parent counter it would have had without any child. Each successful child closes with the ordered `{ text, exact }` runs it rendered. Exact presentation is a provenance recorded against the segment objects expansion marked, and those segments do not cross the join — so the runs -are what carries it, live and on replay alike, and a retained close that does +are what carries it, live and on replay alike, and a saved close that does not read as that shape fails the run instead of rebuilding part of a document. **Output is authored order; the journal is completion order.** A child emits @@ -524,12 +524,12 @@ or completion time. have finished tearing down. No private buffer is emitted, records acknowledged before the failure stay durable, and interrupted work is never recorded as complete. Cancelling the parent takes the same ownership path, and no task, -subscription, retained resource or buffer survives its spawn. No scheduler, no +subscription, saved resource or buffer survives its spawn. No scheduler, no new record family and no document-visible concurrency control is introduced. ## Errors, output and commands -A document decides recovery through its structure. Output routing, retained evidence and process failure remain separate decisions. +A document decides recovery through its structure. Output routing, saved evidence and process failure remain separate decisions. ### Two layers @@ -791,7 +791,7 @@ replacing the failure being classified. ### Component invocation settlement -A component invocation owns projected content, body resources and retained eval +A component invocation owns projected content, body resources and saved eval resources. Teardown stops those three stages in that order and attempts every stage even when one fails. A body failure without teardown failure propagates its original error. Teardown failure alone produces one `InvocationTeardownError` @@ -997,7 +997,7 @@ instead is a conjunction of a private one-use request, a private live boundary credential, and the acceptance record of a terminal created for that one call: the same request-and-terminal division *Authoritative behavior* describes, scoped to the smaller boundary. It is scope-owned and expires with the session -or the element that established it, so a retained credential from a finished +or the element that established it, so a saved credential from a finished boundary activates nothing. Canonical core enforces it, and the placement is the point. What a test *does* @@ -1045,13 +1045,13 @@ fatal search and before any handler is dispatched. #### Output security and host retention -Retained output crosses the pre-persistence secret gate exactly as any other +Saved output crosses the pre-persistence secret gate exactly as any other journaled field does, and the gate examines what the boundary received — after enclosing middleware, not before. Two consequences follow, and both are intended: a host may redact a credential upstream so that the material never reaches the gate at all, and a host that introduces credential-shaped text upstream is refused by the gate like any other run that would persist one. -Output that is never retained is never scanned, because there is nothing to +Output that is never saved is never scanned, because there is nothing to persist and nothing was accumulated for a scanner to inspect. Which host paths retain is a property of the path, stated where the run starts: @@ -1116,13 +1116,13 @@ echo on stderr, the observational output of a value root, and the testing report's own verbosity stay with the host that owns those streams and read the flag directly — so a document can decide what it renders and cannot decide what the process presents. Contextual verbosity decides nothing: it takes no -part in durable identity, authorization, replay admission, retained-effect +part in durable identity, authorization, replay admission, saved-effect ownership or journal identity, and no execution is admitted, substituted or settled differently because of it. ## Workflow state and lifecycle -Retained workflow state makes an execution addressable and replayable. Storage, executor ownership and effects each enforce their own boundary. +Saved workflow state makes an execution addressable and replayable. Storage, executor ownership and effects each enforce their own boundary. ### Workflow runs @@ -1157,7 +1157,7 @@ creates no workflow run and the root document does not expand. `WorkflowRun` is one closed shape carrying `runId`, `bundleHash` and the exact target when it has one. It has no base and no pinned commit: a run starts from -exact retained bytes rather than from a repository state, so a member naming one +exact saved bytes rather than from a repository state, so a member naming one would be a statement about a repository the run never had. It neither creates nor seeds a Workspace Repository. Each `` inside the definition resolves and pins its own optional Repository base, so one workflow may compose @@ -1180,18 +1180,18 @@ capability, and the workflow package calls none of it. Ordinary `execute()` remains Git-independent, and so does starting, resuming, forking or exporting a run. -**The workflow package imports no Git feature, and a retained run reaches no +**The workflow package imports no Git feature, and a saved run reaches no repository either.** The CLI establishes a candidate from filesystem bytes, and -the retained lifecycle re-derives the source from the run's own store, so a run +the saved lifecycle re-derives the source from the run's own store, so a run genuinely needs none: there is no definition whose Markdown lives anywhere but -in the run that retained it, and therefore no source capability for a host to +in the run that saved it, and therefore no source capability for a host to supply. Replay restores the recorded `WorkflowRun` without allocating another run ID or invoking Git. The supplied bundle hash must equal the recorded one. A host that has already created the run's storage record passes -`retainedWorkflowInstallation(run)` instead, with the exact frozen value. +`savedWorkflowInstallation(run)` instead, with the exact frozen value. Nothing is left for the execution to decide: it records that value through the same `workflow_run` durable operation, allocates no identifier and resolves no base, and every journal state — live, truncated and completed — requires the @@ -1208,7 +1208,7 @@ exposes no journal, Git, workspace or continuation capability. Replay preserves the field values, not JavaScript object identity. The `@executablemd/workflow` package owns `WorkflowRun`, -`retainedWorkflowInstallation()`, `getWorkflowRun()` and the generic run, +`savedWorkflowInstallation()`, `getWorkflowRun()` and the generic run, storage, replay and Workspace boundaries. It owns no Git: repository composition, the Git capability, issue and pull-request contracts and the Git-host engine are `@executablemd/git`'s, and that package imports Workflow's @@ -1264,7 +1264,7 @@ against an old identity intercepts nothing, and an import of an old name does not resolve: two vocabularies for one seam is the outcome an alias would guarantee, and a silent miss is what an old identity would produce. The package-internal names that still read `Composition` — the provider -installers, the error classes and the serialized refusal names retained +installers, the error classes and the serialized refusal names saved history holds — are not this layer and did not move. ### Workflow Workspace @@ -1294,8 +1294,8 @@ WorkflowRun SQLite Co-location does not turn arbitrary filesystem content into journal or training data. The journal security policy filters a value before insertion. Training -ingestion deliberately selects retained filtered history; it never receives the -whole database merely because the database is retained. +ingestion deliberately selects saved filtered history; it never receives the +whole database merely because the database is saved. Repository, filesystem, process, Git-host and Agent behavior reaches production code through contextual APIs. A provider adapter installs host behavior; shared @@ -1305,7 +1305,7 @@ workflow capability is absent. ### Workflow run storage A workflow run recorded only in the journal of the document execution that -created it can be found only by whoever already holds that journal. Retained +created it can be found only by whoever already holds that journal. Saved storage is what makes a run addressable: another process opens it by public run ID and continues from durable data. @@ -1377,12 +1377,12 @@ Workspace-root tables, current-root state, a non-null Workspace-root association on every journal event, and the Repository and Worktree creation records #293 added to it. A fresh run starts with one content-addressed root manifest describing only the root directory, empty -retained manifest and blob reference sets, and the corresponding root-only DOFS +saved manifest and blob reference sets, and the corresponding root-only DOFS frontier. `workflow_run` has **no `base` column**, and two tables hold the run's own source: content keyed by its SHA-256 with its byte length, and one manifest row -per descriptor entry mapping a logical path to that hash. Content is retained as +per descriptor entry mapping a logical path to that hash. Content is saved as BLOB bytes rather than as text or re-encoded JSON, and two paths holding identical bytes reference one blob. @@ -1393,11 +1393,11 @@ that inventory is corruption, any other version is unsupported, and an altered or extra object is corruption rather than a migration candidate — there is no earlier shape for a file to be recognized as. -Before a record is exposed, its retained source is re-derived in full: +Before a record is exposed, its saved source is re-derived in full: the manifest equals the descriptor's `sources`, every referenced blob exists, -both retained lengths equal the content's own, recomputing each blob's hash +both saved lengths equal the content's own, recomputing each blob's hash produces its key, no blob is unreferenced, and recomputing the bundle hash from -the retained manifest and mapping produces `bundleHash`. No reader returns a +the saved manifest and mapping produces `bundleHash`. No reader returns a partial bundle, and nothing falls back to the original file or the provenance. A Workspace root is a complete, immutable filesystem checkpoint. Its canonical @@ -1411,15 +1411,15 @@ do not participate in the identity. The root tables are authoritative checkpoints; the DOFS node, dirent and chunk tables are the live materialization of the current root. Files remain solely in DOFS content-addressed blobs. Normalized root-to-manifest and root-to-blob rows -equal the root's transitive content exactly and prevent retained content from +equal the root's transitive content exactly and prevent saved content from being deleted. Opening a run validates every root and referenced manifest and blob, then snapshots the live frontier read-only and requires it to equal -`current_root`. The schema structure, retained content, live/current comparison +`current_root`. The schema structure, saved content, live/current comparison and run row are read through one explicit SQLite snapshot. This recognition transaction is not a caller-owned Workspace transaction and enables no DOFS savepoints. -Recognition also requires every file entry in every retained root, including a +Recognition also requires every file entry in every saved root, including a historical root, to declare the size held by its referenced DOFS manifest. A root whose XMD identity and reference rows are internally consistent but whose file size disagrees with DOFS is corrupt and not restorable. @@ -1431,10 +1431,10 @@ of them, is lifecycle policy applied above storage. Journal events reach storage already filtered, through the existing pre-persistence gate. Storage adds no second filtering policy: a rejected or -cancelled gate leaves nothing behind. A record is retained in the +cancelled gate leaves nothing behind. A record is saved in the representation the protocol produced and parsed back through the matching reader, so replay reads what was written rather than a re-encoding of it. -Events replay in append order. An event's opaque identity is retained +Events replay in append order. An event's opaque identity is saved separately from its physical position; the position orders replay and is not a public identifier. @@ -1454,7 +1454,7 @@ connection. Reopening the same path after provider teardown creates a different generation. Each top-level transaction has its own opaque identity and exact active record, including its path, generation, authorized lease and public transaction handle. Finishing invalidates that record before commit or rollback -can expose the connection to later work. Retained transaction handles and +can expose the connection to later work. Saved transaction handles and tokens stay invalid across a later transaction on the same connection and across a later provider generation. @@ -1511,12 +1511,12 @@ application identity with schema version zero is a partial initialization and therefore damage, not a supported historical version. Records are held to what they mean and not only to the types they are stored -in. A retained timestamp is an instant, a retained identity is not empty, and +in. A saved timestamp is an instant, a saved identity is not empty, and normalized props are an object. Nothing initializes, migrates, truncates, deletes or replaces incompatible or damaged storage, and a lookup that finds nothing creates nothing. Messages -describe what failed without repeating retained props or journal payloads — +describe what failed without repeating saved props or journal payloads — including their member *names*, which can carry a credential as readily as a member value can. @@ -1555,7 +1555,7 @@ store and never rereads the candidate path. A file that is unreadable, not well-formed UTF-8, not Markdown, whose selector resolves to nothing, or that declares a component this command cannot read fails explicitly and before storage exists; none of them creates a replacement run or an empty definition. A -workflow definition is one closed set of retained bytes, so the component search +workflow definition is one closed set of saved bytes, so the component search path is empty and a repository component fails to resolve rather than resolving to content beside the definition in a mutable checkout. @@ -1578,7 +1578,7 @@ The bundle is identity, so compatible reuse compares the whole array: a changed name, canonical path, source hash, or component set conflicts as `definition`, and a run closed over a bundle is never the same run as one closed over none. `resume` obtains the bundle under the executor lock and before the lifecycle -advances. It re-derives the bundle from the run's own retained BLOBs, +advances. It re-derives the bundle from the run's own saved BLOBs, recomputing every source hash and the bundle hash from the bytes it holds; nothing is read from a repository or from the files the run was started beside. A component that changed, went missing, or became unreachable leaves the run's @@ -1592,7 +1592,7 @@ ordinary registered defaults, so core's own components stay available and no name they own can be taken back. Every selected member records one `import_component` result of exactly `{ kind: "workflow", path, sourceHash, content }`, and the installation contributes a journal admission that holds every -retained component import to that exact bundle before public document policy or +saved component import to that exact bundle before public document policy or the root import. While a bundle is installed, `Component.importComponent` middleware may observe, delegate, and refuse an import but cannot answer one: canonical core keeps its own copy of the definition it produced and invokes that @@ -1602,19 +1602,19 @@ each fail before the component is invoked, including one masked by a `toJSON()` or by an accessor that answers twice. Every actual execution opens or creates the run's database, begins a -document-execution record, installs the exact retained WorkflowRun, installs the +document-execution record, installs the exact saved WorkflowRun, installs the service denial before the root is imported, and — for a live or partial execution — installs the logical working directory `/`, the transaction-bound Files provider and that database's Workspace effect coordinator. It executes -against the database's own journal with the retained props and secret detection, +against the database's own journal with the saved props and secret detection, then finishes the execution record and publishes the run's status. A completed -run still replays, so its retained output and result are emitted, but it +run still replays, so its saved output and result are emitted, but it attaches no Workspace provider or coordinator and performs no filesystem mutation. -A failure retains `failed` and uses a journal stop reason when a retained event +A failure retains `failed` and uses a journal stop reason when a saved event identifies it, and a categorical host code otherwise; no exception text is -retained beside the journal that filtered it. Graceful foreground interruption +saved beside the journal that filtered it. Graceful foreground interruption finishes the execution `interrupted` and exits 130. `suspended` is resumable; `failed` and `cancelled` are refused by `resume`, and `completed` replays under either command. @@ -1629,7 +1629,7 @@ decide which adapter exists. The start/resume slice predated single-executor enforcement. Issue #367 replaced its opportunistic orphan closure with the lifecycle ownership below, -and no supported host advances a retained run without it. +and no supported host advances a saved run without it. ### Workflow lifecycle ownership @@ -1643,8 +1643,8 @@ to a host. Routing through a contextual Api selects a provider and grants no permission. Acquisition gives the workflow executor an opaque exact `ExecutorLock` object registered by that provider for one run and one acquisition. The provider checks the same object, its open scope, its run and -the expected retained lifecycle state inside every mutating transaction. A run -ID, database handle, structural look-alike, same-named context value or retained +the expected saved lifecycle state inside every mutating transaction. A run +ID, database handle, structural look-alike, same-named context value or saved token is admitted nowhere. Releasing or tearing down the executor lock invalidates that object before another acquisition can mutate the run. @@ -1652,7 +1652,7 @@ The executor lock is not a time lease. It has no duration, expiry, renewal, heartbeat, watcher, generation record or liveness poll. The operating system releases the local lock when the workflow executor exits. -The shared surface names outcomes and retained data, not a transport: +The shared surface names outcomes and saved data, not a transport: ```ts interface ExecutorLock { @@ -1721,13 +1721,13 @@ interface WorkflowLifecycleApi { } ``` -The two artifact operations are siblings of the retained-run pair rather than a +The two artifact operations are siblings of the saved-run pair rather than a widening of it. A run ID names live lifecycle ownership in this host's storage; a path names immutable evidence somebody is holding, and no run has to exist for it to answer. `WorkflowInspectionSnapshot` is what a run says about itself, and both forms are -it. The retained snapshot alone adds `retrieval`, because where a definition +it. The saved snapshot alone adds `retrieval`, because where a definition could be fetched from now is a fact about the machine holding the run rather than about the run; the artifact snapshot alone adds which artifact answered and the boundary it was sealed at. Neither extends the other. That is what makes an @@ -1746,7 +1746,7 @@ categories actually removed; it contains no statement about external effects. Acquisition has three observable outcomes: acquired, already held by a live workflow executor, or a storage/authorization failure. The initial local CLI reports the active workflow executor rather than following it. Once acquired, -lifecycle changes are compare-and-set transitions against the retained state +lifecycle changes are compare-and-set transitions against the saved state and unfinished document-execution record read in the same transaction. Beginning an execution, finishing its record and publishing the run state are therefore lifecycle transitions, not independent writes another command can @@ -1764,7 +1764,7 @@ enclosing the document execution, and each is atomic within itself. An unfinished document-execution record found after acquisition belonged to the previous workflow executor and is proven stale. If the run says `running`, the -new workflow executor first reconciles retained root history. A root Close +new workflow executor first reconciles saved root history. A root Close restores its canonical terminal state. Without one, execution recovery closes the execution and publishes `interrupted` before beginning another. A cancellation command that @@ -1777,7 +1777,7 @@ row. An execution settlement has one workflow executor and one winner. Canonical completion, failure, suspension and foreground interruption meet at the -execution-owned settlement boundary. A retained root Close proves canonical completion or +execution-owned settlement boundary. A saved root Close proves canonical completion or failure won before interruption or later lifecycle management. A cancellation command never enters this competition: while another workflow executor holds the lock it can only refuse, and after that lock is released there is no live @@ -1787,7 +1787,7 @@ transition waits for the document execution's complete child and resource teardown, then atomically finishes its document-execution record and publishes the run state. Only after that transaction commits may the host report the status and release the -executor lock. If settlement storage refuses, no unretained status is reported +executor lock. If settlement storage refuses, no unsaved status is reported and lock teardown leaves the run recoverable by the next proven workflow executor. @@ -1799,11 +1799,11 @@ infrastructure failures still cross by identity and win by kind; if their lifecycle transition cannot persist, no terminal status is reported. Every finalizer is attempted before any such result is observable. -Retained lifecycle state gates every later acquisition before canonical +Saved lifecycle state gates every later acquisition before canonical execution. A `failed` run carrying its root Close replays that recorded failure for compatible `start --id`. A host-settlement failure with no root Close -reports the retained `failed` state and its categorical or filtered reason -without entering replay. A `cancelled` run reports its retained state without +reports the saved `failed` state and its categorical or filtered reason +without entering replay. A `cancelled` run reports its saved state without entering replay under either command. None can be advanced under the same run ID. @@ -1811,7 +1811,7 @@ The executor lock remains in the trusted host's outer lifecycle scope; it is not installed as a document provider. A completed replay may hold the lock while recording its document-execution envelope, while the run remains `completed` and cancellation remains refused. Canonical core returns the -retained result before preparation or root expansion. It attaches no Workspace, +saved result before preparation or root expansion. It attaches no Workspace, Files, Service, Agent, process or external provider and performs no live durable effect. @@ -1830,7 +1830,7 @@ can address it. The lock file may remain empty after run deletion. Unlinking a locked sidecar would let another process create and lock a different file at the same path while the deleting workflow executor still holds its lock. An empty lock file -is not retained run state and is not reported as history or provider-session +is not saved run state and is not reported as history or provider-session data. #### Read-only lifecycle inspection @@ -1843,7 +1843,7 @@ attach a Workspace, materialize a root, import a document, contact an Agent, process or external provider, or append. Two sources answer the same questions. `inspect(runId)` and `history(runId)` -address this host's retained storage; `inspectArtifact(path)` and +address this host's saved storage; `inspectArtifact(path)` and `historyArtifact(path)` address one sealed `.xmd` file. They are siblings rather than one operation over a union: a run id names live lifecycle ownership — something a caller may still advance, cancel or delete — while a path names @@ -1858,7 +1858,7 @@ ordered rows — authored source, cumulative forkability, inherited provenance because two mappings are how history quietly starts meaning different things depending on where it was read. -The Deno provider first reads each retained database through its ordinary +The Deno provider first reads each saved database through its ordinary read-only connection. A host crash may leave a hot rollback journal: that is a healthy database one SQLite rollback away from the last committed state, but SQLite cannot perform that rollback through a read-only connection. Only the @@ -1866,18 +1866,18 @@ exact `SQLITE_READONLY_ROLLBACK` condition enters recovered inspection. Another read-only failure is reported as itself rather than treated as crash recovery. Recovered inspection acquires inspection recovery coordination and retries the -retained database read-only. If a write-capable owner recovered it first, that +saved database read-only. If a write-capable owner recovered it first, that ordinary source snapshot is the answer. If it remains hot, inspection copies the database and its rollback journal together into a private scratch directory -while it holds the coordination, then releases the retained source and lets +while it holds the coordination, then releases the saved source and lets SQLite recover the copy. The copy keeps the candidate's exact database name and -passes through the same strict schema, retained-content, live/current and +passes through the same strict schema, saved-content, live/current and location recognition as an ordinary snapshot. Recovery discards only the uncommitted transaction, so the resulting recovered inspection snapshot is the -committed state the retained database describes rather than a repaired or +committed state the saved database describes rather than a repaired or alternative run. -Every write-capable connection to a retained workflow database holds the same +Every write-capable connection to a saved workflow database holds the same coordination for as long as it is open. SQLite recovers a hot journal on whichever read first needs a page, and for a connection a host already has that read can come at any moment, from a caller that knows nothing about any of this. @@ -1896,7 +1896,7 @@ The coordination is separate from the executor lock, produces no public capability, authorizes no transition and cannot make an inspector appear to be a workflow executor. Its deterministic empty sidecar occupies a provider-owned non-candidate namespace and may remain after run deletion like the executor-lock -sidecar; it is host arrangement rather than retained state. +sidecar; it is host arrangement rather than saved state. Scratch state is owner-private, belongs to the inspection scope and is processed one candidate at a time. Every success, failure and cancellation attempts its @@ -1904,7 +1904,7 @@ complete removal during teardown. A successful snapshot is observable only after removal succeeds. A permanent removal failure instead returns a typed inspection-recovery storage error after every cleanup attempt; it identifies the host-selected scratch location so the operator can remove it and never repeats -retained content. Cancellation remains Effection control flow unless cleanup +saved content. Cancellation remains Effection control flow unless cleanup itself fails. Private non-candidate scratch may therefore remain only behind an explicit cleanup failure. Inspection never retries removal indefinitely, starts background cleanup or reports successful inspection while such residue is @@ -1913,13 +1913,13 @@ known. Discovery remains arithmetic over the one run-storage root; there is no list registry. Each run contributes one database candidate. Exact advisory-lock sidecar names occupy a distinct provider-owned namespace and are not candidates. -The Deno provider applies the same strict schema, retained-content and +The Deno provider applies the same strict schema, saved-content and live/current recognition as execution to the direct or recovered snapshot. One absent, foreign, incompatible, damaged or unparseable candidate fails the whole list request with its distinct condition. Healthy rows are not returned as though they were a complete list, and every candidate remains unchanged. -Every public history entry is one retained protocol row: +Every public history entry is one saved protocol row: ```ts interface WorkflowHistoryEntry { @@ -1954,8 +1954,8 @@ removes an event from the JSON contract or synthesizes a journal event. #### Portable XMD artifacts An XMD artifact is a semantic export, not a renamed workflow-run database. The -exporter reads one committed retained snapshot and writes a separate sealed -container containing that snapshot, every retained Workspace root and byte it +exporter reads one committed saved snapshot and writes a separate sealed +container containing that snapshot, every saved Workspace root and byte it references, and the workflow definition source closure. Host paths, lock sidecars, rollback journals, open connections, retrieval metadata, credentials and provider-owned session directories do not cross the boundary. The initial @@ -1967,19 +1967,19 @@ artifact. Export takes the executor lock before choosing the artifact frontier, so a live workflow executor makes export refuse and no later committed event can race the chosen frontier. Crash recovery, when required, operates on a private copy under inspection recovery coordination. Export performs no lifecycle -transition and never changes the retained source. +transition and never changes the saved source. what one XMD artifact states about every logical workflow Agent session that contributed -a retained Prompt: either complete portable evidence — the session and its compatibility +a saved Prompt: either complete portable evidence — the session and its compatibility attributes, the provider, the bundle kind and compatibility identifier, the source and bundled provider-session identities, the identity-allocation mode, the ordered provider checkpoint tokens, and the Agent session bundle's length, hash and bytes — or an -explicit unavailable marker naming why. A session that retained no Prompt has none. It -is detached retained evidence about a conversation, never a live provider session and +explicit unavailable marker naming why. A session that saved no Prompt has none. It +is detached saved evidence about a conversation, never a live provider session and never control over the host that produced it Version 1 has two profiles. One holds no Agent portability content and is read -exactly as it always was: a session that contributed a retained Prompt may carry +exactly as it always was: a session that contributed a saved Prompt may carry no classification, and nothing is reconstructed for it. The other classifies every such session exactly once, as Agent session portability evidence — portable, with ordered provider checkpoint tokens and an Agent session bundle, @@ -1993,7 +1993,7 @@ history render no classification, token or bundle byte. Semantic format, container and manifest are each version 1. Identity uses the exact domain `xmd-artifact\0v1\0`; every other declared version is refused. -The workflow source closure is retained entry/content pairs, without a Git +The workflow source closure is saved entry/content pairs, without a Git closure. Every command that consumes an XMD artifact opens it read-only and verifies its @@ -2004,10 +2004,10 @@ so authenticity still comes from the channel that published the artifact. An artifact grants inspection and history-fork input only. It is never resumed, answered, cancelled, deleted as a run, or admitted as the writable storage for -an existing run ID. A history fork from it creates a new run ID, copies retained +an existing run ID. A history fork from it creates a new run ID, copies saved state rather than sharing pages, records the artifact identity with the source run and checkpoint, and passes through the same compatibility replay and -forkability checks as a fork from local retained storage. The artifact remains +forkability checks as a fork from local saved storage. The artifact remains byte-identical before, during and after the fork. This split is the concurrency boundary between environments. Arbitrarily many @@ -2029,7 +2029,7 @@ run resumable; it is not explicit cancellation. Without a live workflow executor, the management host acquires the executor lock before any cancellation mutation. The exact executor lock is validated inside the transaction that publishes `cancelled`. A stale `running` -execution is first checked for a retained root Close: a Close restores its +execution is first checked for a saved root Close: a Close restores its canonical completed or failed outcome and cancellation refuses; without one, the transition finishes that exact unfinished execution as `cancelled`. A suspended or interrupted run has no live @@ -2054,7 +2054,7 @@ Cancellation follows one status matrix: Deletion also acquires the executor lock. A live workflow executor is refused; a stale `running` record is proven stale before the exact run's database and -provider-owned retained records are removed. Only the empty lock file may +provider-owned saved records are removed. Only the empty lock file may remain. Every non-live status may be deleted. The explicit run ID is the confirmation boundary, so the command has no interactive prompt and no wildcard. An absent run is reported rather than treated as an idempotent @@ -2106,13 +2106,13 @@ question and writes no durable record: the wait must be the outermost durable operation at its position, because a request published inside an enclosing durable operation takes the journal slot that operation never settles, and the resumed execution replays the coroutine and finds the wrong entry waiting. And -the answer is therefore retained once, by the suspension protocol, rather than +the answer is therefore saved once, by the suspension protocol, rather than twice. Public Elicitation middleware — an `` region, or anything else installed nearer — may answer the question or refuse it, and in both cases the -run it affected never suspended; continuation of a suspended run is retained +run it affected never suspended; continuation of a suspended run is saved delivery state and nothing else. -The retained request at the execution's exact current durable position is what +The saved request at the execution's exact current durable position is what authorizes entry. The controller derives the identifier for the position immediately behind the caller's own, requires the presented one to equal it, reads the Yield at that exact position, parses it as a suspension request and @@ -2121,8 +2121,8 @@ or later in the journal, a reconstructed contextual API name, or a matching row without the exact current position authorizes nothing. Durable replay identity compares a type and a name, both public, so another -durable operation can reproduce the retained request and arrive at the same -position. Once the controller has validated the exact retained request, that +durable operation can reproduce the saved request and arrive at the same +position. Once the controller has validated the exact saved request, that operation is observationally the same request to wait: it gains nothing that calling `suspendFor()` with that request would not have. Package-instance identity therefore does not take part in admitting a suspension. Requiring it @@ -2152,23 +2152,23 @@ record may settle `suspended`; the request remains singular. #### Typed answer delivery Issue #300 builds typed answer delivery. Its boundary is the suspension ID and -the retained response schema. Delivery is an operation separate from execution: +the saved response schema. Delivery is an operation separate from execution: `xmd workflow answer ` retains one value for that exact wait and takes no executor lock, fetches no definition, attaches no Workspace, inserts no document-execution record, appends no journal event and changes no run status. A host offers a value only after the run is `suspended`, -its stop reason names a retained `suspension_request` event carrying that ID, -and the value satisfies the schema that request retained. The retained state and +its stop reason names a saved `suspension_request` event carrying that ID, +and the value satisfies the schema that request saved. The saved state and the answer event it would become cross the same secret gate durable journal persistence uses; `--no-secret-detection` disables that gate for one delivery. Every refusal — duplicate, consumed, wrong run, wrong request, late, invalid, cancelled-run and deleted-run — leaves the database unchanged, and neither the rejected value nor a secret match appears in a diagnostic. -On a resumed execution, only authenticated retained answer state lets +On a resumed execution, only authenticated saved answer state lets `suspendFor()` publish a separate ordinary durable `suspension_answer` Yield and return its value; a contextual handler's return value never publishes an answer. -Consuming the retained answer and appending that Yield are one database +Consuming the saved answer and appending that Yield are one database transaction, so a crash before it commits leaves the answer pending and a transaction that does not commit publishes nothing. Replay after that transaction commits restores the recorded answer event without reaching the live @@ -2186,7 +2186,7 @@ answer delivery remains with #300. Issue #367 changes no durable schema shape. Single-executor enforcement uses only the provider-owned advisory-lock sidecar, suspension uses existing filtered journal and run-state records, and history reads the existing event-to-root association. -Issue #300's retained answer state does add a durable record, and it amends the +Issue #300's saved answer state does add a durable record, and it amends the one schema in place on the same terms as every other amendment: one `workflow_suspension_answers` table, no second version, no migration, and strict recognition refusing every other shape. @@ -2334,7 +2334,7 @@ that is a deliberate correction rather than a simplification. An earlier design selected a provider on one surface and coordinated the invocation on a second; public middleware could read a credential from the first, take an invocation capability from the second, and answer a phase itself, and the journal then -retained a completion no provider had produced. Two public surfaces that are +saved a completion no provider had produced. Two public surfaces that are individually harmless composed into the power to settle a completion. The single operation is request-only. Public middleware receives one frozen, @@ -2354,7 +2354,7 @@ the public refusing default, never the terminal. The durable operation is named by a fingerprint of the complete detached request, so a changed kind, input or natural key diverges rather than consuming -the result retained at the same position, and the provider's answer is parsed at +the result saved at the same position, and the provider's answer is parsed at the terminal before the durable executor returns, so a raw payload becomes a fixed protocol failure rather than journal content. @@ -2362,7 +2362,7 @@ Some effects need live local state the frozen request cannot describe, and that travels as an object-source attachment rather than as an input. A push needs the Git objects its commit is made of and the destination it may reach; both are built by the trusted host for one reconciliation, authenticated against the -retained identity they belong to, and held in the selected provider's closure. +saved identity they belong to, and held in the selected provider's closure. Nothing about an attachment reaches the durable request, the natural key or the routing surface, so what discriminates the effect stays the filtered identity a journal may hold while what performs it stays where no replaceable code can @@ -2408,12 +2408,12 @@ Workspace and the external service, and reconciliation rather than a distributed transaction is what makes an interrupted attempt reach the host once. A provider that needs a program must also not inherit configuration the run -merely stores. A checkout's `.git/config` is retained document data, and several +merely stores. A checkout's `.git/config` is saved document data, and several ordinary settings in one name a destination or a program — a push URL, a URL rewrite, a hook, a credential helper, an SSH command, a signing program. The Deno push provider therefore runs its transport in a disposable repository of its own that reads the selected checkout's object database through a read-only -alternate, and aims it at the exact private retained locator rather than at a +alternate, and aims it at the exact private saved locator rather than at a configured remote. The two settings that would otherwise choose how a remote operation authenticates — `credential.helper` and `core.sshCommand` — are fixed on the command line, where they outrank every configuration file, for every @@ -2422,12 +2422,12 @@ invocation that transports to a remote. #### Invocation-owned authentication Authentication itself is the one live host input such an invocation borrows, and -it is not the same question as inherited configuration. Retained state says +it is not the same question as inherited configuration. Saved state says which repository an effect belongs to; it must never say who the run is, because an identity belongs to the machine a run is standing on and a run resumed elsewhere authenticates as whoever is there then. So one live provider invocation — a Repository creation, a Push reconciliation, a pull-request or -Issue call — opens one authentication session lazily, for its own exact retained +Issue call — opens one authentication session lazily, for its own exact saved locator, after the admission checks that operation requires. Every native command that invocation runs attaches that same session, so an observation and the mutation it decided go out under one identity rather than two, and the @@ -2460,7 +2460,7 @@ channel to it — so an acquired credential with no rejection keeps the ordinary transport classification rather than acquiring a new one. A session opened for one locator never authorizes another, nothing about one is -retained, and a completed replay opens none at all because it performs no remote +saved, and a completed replay opens none at all because it performs no remote operation to attach one to. A missing or rejected credential is ordinary live refusal: it proves no absence and adopts no completion, and a transport that carries an identity attempted with no mechanism to prove one is refused under @@ -2593,29 +2593,29 @@ completed Push is reconstructed from the root its own journal event was appended against, which is the association the run made when it wrote that event, rather than from the frontier the run reached. That reconstruction is a read: the historical root is materialized, exported from, and discarded on every path out, -leaving the current root, the retained roots, the metadata and the journal +leaving the current root, the saved roots, the metadata and the journal exactly as they were. It decides only which question is asked, because the request it produces still has to be the one that position asked before the -retained record answers it. +saved record answers it. -#### Retained effects and history forks +#### Saved effects and history forks -An external effect is judged by its retained record rather than by its type. A +An external effect is judged by its saved record rather than by its type. A completed Git-host reconciliation record replays without contacting a provider, so a fork inherits it and the remote is mutated once; the record keeps the identity it was written under, and the effect at a position the history already holds a record at is named by that identity, while every live attempt is named by the run performing it. The identity is established when the engine -admits the run's retained snapshot and is published beside the run, so every +admits the run's saved snapshot and is published beside the run, so every physical copy of the workflow package reads one answer. What authorizes live -execution is not that answer: a coroutine that walked away from retained history +execution is not that answer: a coroutine that walked away from saved history it never consumed performs no Git-host effect for the rest of its run — the engine's own account decides it, at every position after the divergence — so removing or replacing the transported identities can only make the effect replay -correctly or refuse. It belongs to the exact retained event it +correctly or refuse. It belongs to the exact saved event it came from, at the position that event occupies: records are offered in order, a call may take only the next untaken one and only when it asks exactly what -that record answers, and a request named by a retained record that reaches live +that record answers, and a request named by a saved record that reaches live execution performs nothing rather than performing under another run. A Git-host event that settled into no such record leaves the remote's state unestablished, and that checkpoint is not forkable. @@ -2676,7 +2676,7 @@ teardown or final-validation failure, and commit failure. The surviving wrapper therefore cannot answer from uncommitted positive or negative cache entries after SQLite has restored the prior frontier. -Retained roots, manifests and blobs remain indefinitely. Cloudflare garbage +Saved roots, manifests and blobs remain indefinitely. Cloudflare garbage collection is not in the production closure and is never invoked. The provider exposes no public history selection at this layer; a fork's root copying is a lifecycle transaction above it. Its @@ -2710,10 +2710,10 @@ That is the same boundary every other reader already observes rather than a second rule for crashes. A second connection sees the last committed state for as long as the writer's transaction is uncommitted, so a crash publishes nothing that was not visible before it. What a later process finds is that -committed state — the filesystem, the current root, the retained roots and +committed state — the filesystem, the current root, the saved roots and references, and the ordered journal with its event identities and root associations — from which it performs no recorded effect again and can -materialize any retained root. +materialize any saved root. The Deno journal adapter routes an append ordinarily when no destination is bound. A publication may instead bind one exact transaction destination for its @@ -2768,13 +2768,13 @@ document replay returns its recorded result without expanding the document and therefore starts no service. Ordinary durable eval transforms a block and validates its declared exports against the live overlay before constructing its durable effect. A collision therefore has no eval `Yield`; compatible partial -replay remains aligned, while retained history containing a now-incompatible +replay remains aligned, while saved history containing a now-incompatible successful eval is rejected by the existing replay guard or divergence path. -Every root and child termination checks its retained subtree before appending a +Every root and child termination checks its saved subtree before appending a `Close`. The replay index tracks which coroutine identities the current -definition uses, so a completed retained child is not mistaken for aligned +definition uses, so a completed saved child is not mistaken for aligned history merely because it already has a `Close`. Durability failures take -precedence over ordinary termination even after the last retained `Yield` was +precedence over ordinary termination even after the last saved `Yield` was consumed. If collision handling terminates immediately, `TerminalDivergenceError` retains the collision as its cause and the history receives no terminal event; restoring the compatible definition can still @@ -2879,7 +2879,7 @@ inside one savepoint and one `workspace_file` effect: successful creation or existing-directory adoption publishes the resulting root with the recorded success, while a documented refusal rolls every directory created by the attempt back and publishes the refusal against the unchanged root. An ordinary -refusal from any mutation follows that rule, so the retained outcome describes +refusal from any mutation follows that rule, so the saved outcome describes a Workspace that is exactly what it was, and the reason that crosses the boundary is selected from the shared vocabulary rather than derived from anything the filesystem said. Replay restores those recorded outcomes without @@ -2887,7 +2887,7 @@ performing the mutation, opening a transaction or consulting the current frontier, which is why a create/delete/create history replays in order — and why a completed ensure does not create again. A cancellation between a mutation and its commit publishes nothing, so continuation performs it once against the -retained frontier. A temporary directory is refused outright: the provider has +saved frontier. A temporary directory is refused outright: the provider has no host directory to hand out, and falling through to the caller's would be the uncontained filesystem the boundary exists to prevent. @@ -2961,7 +2961,7 @@ commit — in the provider instance's own closure. A checkout with no admitted Ordinary PullRequest upserts and evidence reads share the URL matching, host ceiling, response normalization and low-level GitHub reconciliation, and differ in lifecycle and -admission. A read is performed afresh every execution and retained nowhere. An upsert +admission. A read is performed afresh every execution and saved nowhere. An upsert authenticates the Repository selection and the contextual checkout, reads the current named branch and commit, and requires the exact matching entry this provider instance already holds — a Push for another checkout, Repository, origin, destination, branch or @@ -3025,7 +3025,7 @@ first `` that needs one — never at installation — and kept in th closure for the rest of the execution, including when the host can name none. A run that commits nothing never asks. It is used for an ordinary `` and nothing else, because that commit lands in the caller's own checkout; a workflow run keeps its one -fixed identity, whose whole purpose is that retained Git state does not depend on whose +fixed identity, whose whole purpose is that saved Git state does not depend on whose machine made it. It is not a prop, a Context value, a component result or a middleware answer, and nothing else about the environment is borrowed with it — hooks, file-system monitors, signing programs and repository-supplied credential helpers stay disabled by @@ -3037,7 +3037,7 @@ every other component is unaffected. An ordinary invocation identity is a fresh opaque random value the document execution's repository provider mints for itself and keeps in its own closure. It is not a prop, a -Context value, a component result, a middleware answer, a lifecycle ID or a retained +Context value, a component result, a middleware answer, a lifecycle ID or a saved record, and it is neither addressable nor reusable; live Issue and pull-request idempotency and reconciliation keys are derived from it together with the engine's own expansion identity. @@ -3048,7 +3048,7 @@ An Agent proposes source; trusted execution admits and performs it. Session cont ### Native Agent sessions -A native handoff gives one exact retained conversation to the provider UI. Its +A native handoff gives one exact saved conversation to the provider UI. Its construction identity, executable compatibility and live owner are separate facts. Read these rules together with the [native launch interface](specs/native-agent-session-launch-spec.md) and [ACP provider @@ -3088,11 +3088,11 @@ identity exists. A `` or `` meeting a bound client-allocated ro attaches under the route's exact identity; a legacy unbound route or an unavailable attachment capability refuses before a turn and creates no substitute conversation. -Phases are retained as `agent_session_launch` records under one expansion identity — `prepared` +Phases are saved as `agent_session_launch` records under one expansion identity — `prepared` before ownership is released, then `materialized` where an adapter required a materialization turn, then `detached`, then `exited` — so a completed replay launches nothing, a replay holding only `prepared` proves the handoff never began and -may still create under the retained identity, and one holding `detached` resumes and +may still create under the saved identity, and one holding `detached` resumes and never falls back. The public route carries an opaque one-use launch request and answers nothing; the right to run and retain a phase is delivered to the installed provider directly, so neither a returned completion nor a rebuilt request authors a launch. @@ -3112,13 +3112,13 @@ message. No launch path discards persistent provider state. A session whose adapter pins a build is bound to that one build, whichever side named it: the build is observed inside ownership before an identity is allocated or a provider session is created, the binding is published with the bound route — V2 for -client-native, V3 for ACP-first — and retained beside the prepared record, the native +client-native, V3 for ACP-first — and saved beside the prepared record, the native child runs the exact observed path in place of the launcher name, and every later create, resume, attachment and incomplete replay reobserves and compares before a process, an ensure or a turn. A `` or `` meeting a bound client-native route attaches to it: it -reobserves the build, requires any retained provider arrangement to assert that same +reobserves the build, requires any saved provider arrangement to assert that same conversation, calls ensure with the route identity as `resumeSessionId`, and requires the provider to report that identity before a turn — refusing on missing capability, build drift, missing history or a differing assertion without creating a substitute @@ -3147,7 +3147,7 @@ reads at a thread's first turn. That tuple is the compatibility point the advert stands on rather than an allowlist: admission accepts one canonical `codex-cli x.y.z` line, so another canonical version is admitted while bare, malformed, ambiguous or multi-line output refuses; exactness belongs to the session, whose accepted string becomes its -retained executable binding and is reobserved and compared before every later create, +saved executable binding and is reobserved and compared before every later create, resume, attachment and incomplete replay, so continuing a session across releases is not offered. Reading the ACP session id as the native identity stays forbidden; `Agent.AddDir` is unbuilt. @@ -3202,7 +3202,7 @@ A prompt that started and a successful launch preparation retain one canonical `configuration`; a configuration refusal and an unconfigured operation retain none. Completed replay contacts no provider, and an incomplete configured launch reapplies -that canonical value to the exact retained Session or fails. +that canonical value to the exact saved Session or fails. Released launch `requestedModel` and observational `model` strings are compatibility-reader inputs only: valid values are discarded, malformed values refuse, @@ -3213,7 +3213,7 @@ native UI; a final configured client-allocated launch refuses before route, iden private file or process, while middleware that routes an unconfigured Session receives the unchanged unconfigured contract. -`Agent.options()` reports provider-ordered, grouped choices through one scoped unretained +`Agent.options()` reports provider-ordered, grouped choices through one scoped unsaved inspection session that sends no prompt and creates no XMD execution, journal or durable session, and `xmd agent options` renders them as text or version-1 JSON. @@ -3240,7 +3240,7 @@ constructed. Session materialization is the transition that makes a placement's chosen route and its backend history resumable. ACP-first materialization happens only when the backend reports that it accepted the session's first turn; client-native materialization is the -native launch's existing retained construction. Nothing else promotes a placement — not +native launch's existing saved construction. Nothing else promotes a placement — not a returning ensure, a first output, a terminal result, a checkpoint token, an error code or a diagnostic. @@ -3248,7 +3248,7 @@ or a diagnostic. An established session is a placement whose immutable construction route and durable provider or native identity both already exist, and which is therefore validated -eagerly: reattached, compared against its retained history, and refused when either is +eagerly: reattached, compared against its saved history, and refused when either is missing or names another conversation. #### Instruction placement @@ -3319,7 +3319,7 @@ An executable observer is the host-built capability that resolves a launcher com through the host's real execution environment, canonicalizes the target, requires an executable regular file, hashes that target and asks that same file its version. The host passes it directly into the provider like the coordinator beside it. It is -deliberately not contextual: executable validation decides which retained history may be +deliberately not contextual: executable validation decides which saved history may be accepted, and a resolver document middleware could replace could point the observation at one binary while the run spawns another. @@ -3336,7 +3336,7 @@ joining it prove different things. Identity provenance records who chose a provider-native session identity: the provider returned it, or the client allocated it before the provider existed. This provenance is -retained rather than inferred, because after the fact both are just a string in the +saved rather than inferred, because after the fact both are just a string in the record. A released record written before the distinction existed reads as provider-returned, which is the only compatibility inference. @@ -3371,7 +3371,7 @@ public permission chain. This is the V1 permission ceiling; the portable proof that every provider exposes no ambient tool is tracked separately and does not widen it. `xmd run` keeps its caller-selected Agent permission behavior. -What a turn was allowed to do is retained beside what it said, and the two are +What a turn was allowed to do is saved beside what it said, and the two are recorded by different things. A permission policy decides every request inside its scope without deferring outward, so nothing installed inside one — and nothing installed inside the turn, below a policy a host put around the execution @@ -3529,7 +3529,7 @@ resumes. An interruption before the mapping commits is reconciled only from one canonical provider assertion — exactly one, for that identity. A missing assertion, an -assertion conflicting with what is retained, one that replaced it, and more than +assertion conflicting with what is saved, one that replaced it, and more than one at once each refuse. Once the mapping has committed, a restart compares the provider's current assertion against it before continuing, and reattaches only when the kind and the value both still agree. No refusal substitutes a new @@ -3539,9 +3539,9 @@ Cancellation owns both sides of a live step. It stops the Prompt stream or the generated-XMD evaluation, completes provider and evaluator teardown, and removes the disposable directory before the attempt settles. Already committed session, Prompt, admission and effect records remain valid; an uncommitted read or -mutation may run again under the same retained admission, on its own +mutation may run again under the same saved admission, on its own component's ordinary transaction and retry terms. A final proposal is the -retained result of its Prompt. Any later execution of its mutations crosses the +saved result of its Prompt. Any later execution of its mutations crosses the separate mutation-proposal admission rather than an Agent channel. Generated XMD is untrusted input. The evaluator preflights the complete fragment @@ -3553,7 +3553,7 @@ and fragment-local binding references remain available; pure components such as Json consume those values without effect permission. Generated bindings neither import caller values nor escape the fragment. The evaluator retains the exact filtered generated source for replay, history and deliberate training -ingestion. An incomplete evaluation can continue from retained source without +ingestion. An incomplete evaluation can continue from saved source without invoking the Agent again; a completed bounded information request reproduces its rendered findings without repeating reads. @@ -3620,7 +3620,7 @@ that a workflow run offers the name at all; an ordinary run offers none. A host's own extensions are names it answers for. Selecting `write` intentionally authorizes Dir's persistent recursive directory creation as well as the mutations File and File.Delete perform. Each is the ordinary component and crosses `API.Files` -into the run's transaction-bound provider, so an ensured directory is retained +into the run's transaction-bound provider, so an ensured directory is saved by the same `workspace_file` effect an authored Dir publishes. The evaluator adds no mutation API, transaction or receipt of its own. A write-enabled fragment that only ensures a directory renders nothing unless the fragment @@ -3631,11 +3631,11 @@ are outside the class. The identity a spelling runs under is a grant boundary, not a symbol decoration. An earlier `` grant authorized lexical placement that created nothing, and it does not authorize the persistent mutation this one does. A -continuation whose retained rows name a different identity for that spelling is +continuation whose saved rows name a different identity for that spelling is refused before generated execution instead of inheriting a broader grant. Read-only admissions never reached the writing spellings and remain unaffected. -The retained rows are the grant rather than a presentation, and their order is +The saved rows are the grant rather than a presentation, and their order is derived from the selection rather than from how a host assembled it: one row per selected spelling, in canonical name order. A continuation is compared against them row by row, so adding the deletion spelling refuses a continuation that was @@ -3652,13 +3652,13 @@ diagnostic and in what it retains alike, because the decision is recorded either way. A request the candidate malformed is a different thing: whether a request is well formed at all is the component's own question, asked where the request is made — which is after the admission. Admission is the line: before it nothing of -the candidate is retained, and after it the exact source is retained on purpose, +the candidate is saved, and after it the exact source is saved on purpose, so an ordinary component diagnostic naming part of the fragment discloses nothing the journal does not already hold. -A retained admission is a grant, and a resumed run is held to it. Durable +A saved admission is a grant, and a resumed run is held to it. Durable replay matches an effect by its type and name, and what a description carries -is stored rather than compared, so the normalized policy is retained in the +is stored rather than compared, so the normalized policy is saved in the admission's own result and a continuation checks it before one generated component is invoked or one effect is performed. Every term compares whole and exactly, in canonical order: the class selection and the admitted spellings with @@ -3666,12 +3666,12 @@ the identity each runs under. What is deliberately absent is everything live. The run's own progress is not a term — every committed mutation retains another immutable root and advances the -authoritative current root, and a grant compared against a retained copy of that +authoritative current root, and a grant compared against a saved copy of that frontier would refuse a run for exactly the work it was admitted to do. Nor is a request ceiling: which destination an admitted read may reach is asked where the request is performed, against the effective `Permissions` of that scope, so a -continuation held to a retained copy of it would refuse a run whose -configuration legitimately moved. The retained policy holds one row per admitted +continuation held to a saved copy of it would refuse a run whose +configuration legitimately moved. The saved policy holds one row per admitted spelling: the name, the authored spelling, the absolute `https://executable.md/` identity that spelling runs under, whether canonical core supplies the body or the ordinary import chain answered for it, and which @@ -3700,7 +3700,7 @@ yielded inline by the owning document expansion, in authored order and ahead of the expansion's later effects, so one durable sequence accounts for the admission, each nested generated effect, and whatever the document does next. A partial continuation is offered that same sequence: the admission and every -completed nested read or mutation restore from their retained records without +completed nested read or mutation restore from their saved records without another live execution or root publication, and the run continues to the next unrecorded position. The existing `scoped()` around generated expansion keeps owning its middleware, cancellation, and teardown. The evaluator is an @@ -3713,7 +3713,7 @@ comparison rejecting the run's own progress. An XMD-mediated HTTP read is how a network-denied Agent is given external information: the authored document performs the read with `` and the -retained response reaches the prompt as data. That does not raise the Agent's own +saved response reaches the prompt as data. That does not raise the Agent's own ceiling. A generated fragment may name `Fetch` only when the host selected it, and what an admitted read may actually reach is the live terminal's question: a host that bounds its requests states that bound as a `Permissions` @@ -3779,7 +3779,7 @@ them but core. What an admitted spelling then runs as comes from that definition's own declaration: an absolute `https://executable.md/` identity and one of the two effects, per authored -spelling, read where the definition is published and retained with it. So the selection +spelling, read where the definition is published and saved with it. So the selection says which names and spellings a fragment may write, and the owner says what writing them runs and which class it reaches — a spelling whose owner offers no generated use of it refuses at capture rather than acquiring a grant from the selection that named it. @@ -3907,7 +3907,7 @@ or refresh information to validate history. One narrow namespaced classification identifies the normalized recoverable generated-request refusal and carries only a safe reason. Stale admission or malformed -retained history, and terminal setup, runtime, provider, persistence, secret and +saved history, and terminal setup, runtime, provider, persistence, secret and cleanup failures, are not that classification and are never recovered. Normalization reads the namespaced descriptive tag across loaded copies rather than matching messages, and a plain `GeneratedXmdError` is not a recoverability @@ -3927,7 +3927,7 @@ owns directory placement, lifetime and the three validation times. #### Delivery status and standing of this amendment -The current contract is below. Its earlier delivery sequencing is retained as +The current contract is below. Its earlier delivery sequencing is saved as [Plan information-request history](specs/decisions.md#dec-017-plan-information-requests--delivery-history). The [Plan specification](specs/plan-command-spec.md) owns the authored interface and product verification. @@ -3991,7 +3991,7 @@ Canonical protection fixes which implementation answers for a name canonical core owns. Five names are in that tier: Syntax, Evaluate, File, File.Delete and Dir. Selection answers them above the repository, default and host tiers, and the final `Component.importComponent` answer is verified against the copy -canonical execution retained — so a registration, a repository file, a bundle +canonical execution saved — so a registration, a repository file, a bundle member, a declared Markdown component, a middleware replacement and a definition from a second loaded copy are each refused where they are made. @@ -4054,7 +4054,7 @@ runtime failure rather than a preflight refusal. Each `` under an `` is checked from that same incoming snapshot and contributes nothing back, which is the binding contract the children actually run under. ``, `` and `` bodies read and write the enclosing environment. Constructs keep their -exact spelling in the retained source, never join the retained named-component +exact spelling in the saved source, never join the saved named-component list, and change no record version. A trusted host may add another pure component only by placing @@ -4067,7 +4067,7 @@ surface. Each evaluation receives a fresh narrowed protected-body route. It closes with the evaluation, before another Agent turn. Continuation verifies current -canonical identity and retained structural identity before rebuilding a route. +canonical identity and saved structural identity before rebuilding a route. Same-name replacements, copied functions, middleware substitutions, another loaded core copy and expired routes do not inherit protection. No protected function, live profile, provider or capability handle enters durable history. @@ -4237,7 +4237,7 @@ participates. Plan does not install a transient execution around its content. Each admission or effect record is appended before its operation resumes and becomes ordinary replay history immediately. Evaluate does not stage, hide, publish or discard another component's records and changes no persistence -contract. Two Evaluate occurrences cannot consume one another's retained work. +contract. Two Evaluate occurrences cannot consume one another's saved work. A completed request replays without another Agent turn, fragment body, Syntax lookup, File read or Glob traversal. Re-entry needed to validate structural @@ -4254,7 +4254,7 @@ and filesystem scope/policy identity. There are no bound values to compare. It uses stable structural identity, never function serialization or replaceable context state. Extra effect-description fields alone do not enforce comparison. Changing any bound fact is stale before another turn or read. Absent, malformed -or inconsistent records fail closed, including a retained refusal for different +or inconsistent records fail closed, including a saved refusal for different source. Authoritative checks cannot be skipped by public replay middleware. Unchanged completed findings remain historical information. Replay does not @@ -4429,7 +4429,7 @@ because that is what the run turned out to be. A second delegation adds no second expansion, import or authored effect, while the work the first valid delegation already did stands. A preparation refusal prevents every later preparation, all document policy, the root import and every authored effect, -while durable effects earlier preparations completed stay retained and are not +while durable effects earlier preparations completed stay saved and are not rolled back; the root records the refusal as its terminal. A failure raised before the root import is whatever a handler or a preparation @@ -4500,7 +4500,7 @@ replays the recorded failure — running no preparation, no policy, no import and no authored work, and appending nothing — while a different root source or target is refused, and a missing, malformed, duplicated or conflicting binding fails closed. A partial history is re-entered: a preparation that already -recorded a durable effect finds it retained and restores it rather than +recorded a durable effect finds it saved and restores it rather than performing it again, and a refusal that follows records a fresh terminal. A history with no recorded completion is unaffected: it continues live, and preparation runs again through the ordinary durable protocol. @@ -4508,7 +4508,7 @@ live, and preparation runs again through the ordinary durable protocol. Terminal acceptance stores a **detached** structural copy of the accepted props: objects and arrays are recursively copied and frozen, `"__proto__"` is carried as an ordinary data member rather than assigned, and no container the caller -still holds is retained. Anything that is not JSON, and any object that resists +still holds is saved. Anything that is not JSON, and any object that resists being read, is refused as a `DocumentProtocolError` built here rather than rethrown — so neither a hostile trap's message nor the value that produced it reaches the caller. @@ -4554,8 +4554,8 @@ request canonical core issues and consumes. The request retains the authored modifier chain and the block context; both are frozen when it is issued. Public middleware composes around the operation on the same terms it composes anywhere: it may observe the request, refuse by throwing, and delegate. It -cannot produce the command. Canonical core authorizes and composes the retained -chain and runs it against the retained context, so a rewritten, mutated, copied +cannot produce the command. Canonical core authorizes and composes the saved +chain and runs it against the saved context, so a rewritten, mutated, copied or fabricated request — and a `codeBlock` handler answering with another block — runs nothing rather than running something else. The outcome goes to the expansion that issued the request rather than through the operation's return @@ -4577,7 +4577,7 @@ still reaches a registered modifier by name, a replaced `timeout` included. #### Capability-backed invocation identity -A component that names a durable operation after itself decides which retained +A component that names a durable operation after itself decides which saved record a replay restores, so two invocations arriving at one name each replay the other's work, and an implementation running under somebody else's identity commits against its own storage under their expansion. A decision like that @@ -4655,7 +4655,7 @@ working directory is installed for their descendants at all, ``, ``, `` and `` turn a content-bearing invocation into a refusal, and `` and `` decide what body is committed or published. Each reads the contextual answer, so middleware may alter the -effect, the guard or the retained data each of those branches decides, and this +effect, the guard or the saved data each of those branches decides, and this invocation boundary makes no stronger guarantee for them. **Which domain an invocation is in is decided by what resolution selected, not @@ -4918,11 +4918,11 @@ and declared components are copied and frozen **before** any installation runs, so what ends up authoritative is fixed before any installed code, any middleware and any document code exists. Each runs inside the execution's own journal read, on the -retained snapshot, in capture order, stopping at the first refusal — ahead of +saved snapshot, in capture order, stopping at the first refusal — ahead of root-history admission, `ReplayGuard`, terminal reuse, authored work and any append. -Admissions are refusal-only functions: they receive the retained history, return +Admissions are refusal-only functions: they receive the saved history, return nothing, and never receive a `next`. They travel as values the host holds and passes — not through a context, an Api, a stable name, structural metadata or module-scoped state — which is why a separately loaded package composes here by @@ -4950,13 +4950,13 @@ inside `install()` changes nothing. It travels as a value on the same terms an admission does, and is reachable through no context, Api or name. Preparations run in installation order, stopping at the first refusal, **after** -retained-history admission and **before** any public `Execution.document` +saved-history admission and **before** any public `Execution.document` policy, the root import, and every authored effect. They are `Workflow` operations, so what they record is journaled: on a completed terminal replay the durable root is already settled and no preparation runs at all, while on a live or partial continuation they run and record through the ordinary durable protocol — an effect an earlier preparation already completed is restored from -its retained record rather than performed again. +its saved record rather than performed again. #### The weak journal-provenance association @@ -5183,7 +5183,7 @@ could not write is diagnosed on the same terms the document would have been. **No durable protocol changes.** There is no Plugin event, no Plugin-set identity and no generic Plugin record. The root import, the declaration records and every existing encoding are byte-identical, and a Plugin whose future -middleware must reject an incompatible retained history supplies an explicit +middleware must reject an incompatible saved history supplies an explicit `JournalAdmission` rather than having one inferred from its name. #### Where a consumer imports from @@ -5296,7 +5296,7 @@ document the host runs. the kind before any other member and immediately refuses an unknown kind. For a known kind, the invocation retains that discriminant and its arm's members — factory bound, arrays and schemas copied — on the same terms as admissions and -the bundle. The branch and the retained kind therefore come from one read, even +the bundle. The branch and the saved kind therefore come from one read, even if a later read would answer differently. A schema is copied rather than referenced because it is a whole object graph, and holding the caller's object would leave the contract mutable after capture. A host that hands a declaration @@ -5315,7 +5315,7 @@ gains no callee-private names but retains the caller's own lexical private names Eligibility belongs to the authored occurrence rather than to the name. What a private import may invoke is the object canonical core produced inside that ask, -so an answer a handler retained from a legitimate private import authorizes +so an answer a handler saved from a legitimate private import authorizes nothing at a later site — and because neither the name nor the current execution is what is checked, it authorizes nothing under an alias, inside a copy of the definition, after the invocation that produced it is over, or in a later @@ -5406,7 +5406,7 @@ execution, the registry `xmd syntax` and structural validation enter, ``'s internal admission validator, and `xmd plan`'s candidate validation. A Plan is therefore checked against the vocabulary the later `xmd run` supplies, rather than a narrower one that would refuse a program the run would have accepted, and the -three checks inside one Plan command consume one retained assembly rather than +three checks inside one Plan command consume one saved assembly rather than reinstalling and re-reading every packaged asset. The Plugin reserves the forty-one names for the commands that can run a review — @@ -5642,7 +5642,7 @@ provenance is descriptive data and never a callable lookup key. Routes, projection edges, bodies, domains, callable sets and site operations belong to one live execution. Child closure and execution teardown invalidate -retained operations. A same-name function, structural clone, independent wrapper, +saved operations. A same-name function, structural clone, independent wrapper, unadmitted definition or implementation another loaded copy built gains no route. Durable records keep their existing structural identities, forms, source, profile inputs and results; they contain no callable permission. Continuation @@ -5757,7 +5757,7 @@ one member — `{ symbols: string }` for a successful rendering, or `{ refused: non-empty string }` for a named selection canonical core refused. Both are durable values, because a failure crossing the durable boundary is rebuilt without its class; the component interprets the value only after -publication succeeds, so a retained refusal means the same thing live and on +publication succeeds, so a saved refusal means the same thing live and on replay while a publication failure prevents interpretation and stays terminal. A continuation hostile-parses that record and answers from it without consulting the filesystem, the registry, the bundle, the host or the lexical reference @@ -6068,11 +6068,11 @@ middleware answer, document prop or durable record, and not on ordinary `execute()`. Core parses, name-validates and recursively copies the whole record before any installation or document code runs, and the detached values are ordinarily mutable inside the execution that receives them. The REPL derives -that record only from values durably retained before the new entry's admission, +that record only from values durably saved before the new entry's admission, so replay derives the identical record from the identical bytes. The session owns at most one entry task. Starting another needs two separate -facts — the preceding entry has a retained terminal close, *and* its task has +facts — the preceding entry has a saved terminal close, *and* its task has completely torn down and joined — because observing a terminal outcome is not the same as the work being over. There is no queue: a submission that arrives before both hold is refused, having started nothing, and teardown stops a @@ -6080,8 +6080,8 @@ pending one from starting rather than appending on its behalf. Settled segments are projected and never replayed; only an unfinished final segment is given back to Core to continue. -A location is resolved against the retained history before a session opens. -Opening one starts or resumes the execution, and replay past the retained prefix +A location is resolved against the saved history before a session opens. +Opening one starts or resumes the execution, and replay past the saved prefix appends — so a route naming a scope or drawer the file never held is answered from the records already on disk, where being wrong costs nothing. Resolution takes one fact the model cannot hold: whether this process is asking a question. @@ -6102,7 +6102,7 @@ runtime. Every pane reads the selected prefix. The History band and its drawer are the explicit exception, and the only one: they read a thin summary of the *whole* -validated file — a marker and a kind per retained position, plus the head — +validated file — a marker and a kind per saved position, plus the head — because the positions after the selected one are where a reader can go next. No label, source, name, value, outcome detail or observation is in it, so nothing a later position holds reaches a reading of an earlier one. @@ -6120,7 +6120,7 @@ The session hands both halves over together, as one reading, and the root carries that one value through resolution, paint and resize rather than asking again for either half. It has to: the root reads the terminal's size between resolving a frame and composing it, that read suspends, and a record can be -retained while it does. Content taken before it and navigation taken after it +saved while it does. Content taken before it and navigation taken after it would describe two different files on one screen. A resize recomposes the reading that was resolved at a new size, never at a new snapshot. @@ -6131,7 +6131,7 @@ resolve at its position: there, a reading still stands and the reason is said beside it. A refusal has nothing to stand beside, so nothing is left on the screen pretending to be current. -The head is derived on read from retained facts and from work this session +The head is derived on read from saved facts and from work this session owns, never from a body's return, a recorded root Close, a component badge or the selected content. It changes while the file stands still, so it publishes through the wake the session and the controller already send; History adds no @@ -6168,14 +6168,14 @@ scope, which is what lets the REPL drawer answer a generated question and an Permission composes outermost-first: Core's own audit observer, then this session's authoritative policy, then whatever the document and provider installed, then the base denial. One private ledger per inherited Prompt scope -holds what was granted, and a retained audit is read rather than answered. The +holds what was granted, and a saved audit is read rather than answered. The live overlay, the application and the terminal have one owner each — the session, the root transition boundary and the screen — and every ending cancels and joins that owner rather than appending on its way out. Ownership in the model is read from the Journal and from nothing else. An -import's retained selection says where its scope's source came from — a path for -a component read from somewhere, the retained origin for one the host declared — +import's saved selection says where its scope's source came from — a path for +a component read from somewhere, the saved origin for one the host declared — and that origin is the path the effects inside it are recorded against. A generated fragment has no file, so the work inside it names the admission it was decided under, and the scope that admission created is what owns it: the @@ -6222,7 +6222,7 @@ record or public Core Api carries one. A Prompt's sequence number is local to the execution that recorded it, so it restarts with every entry and two entries may each hold sequence `0`. Turns are therefore ordered by entry admission first and by sequence within that entry, -and uniqueness is required only inside the owning entry. Both retained and live +and uniqueness is required only inside the owning entry. Both saved and live turns carry their entry beside their scope, and a publication is correlated by the pair — a durable Prompt name says where it was written, so two entries running one source hold the same name at the same position, and correlating by @@ -6241,7 +6241,7 @@ does nothing else. Activating it records that request's opaque key in process-local state — never in the location — and opens the Sessions-only `+permission` drawer; the reducer decides and `program.ts` alone holds the authority that answers, so the drawer closes only when the request is really -settled and a stale, unknown or unoffered action settles nothing. A retained +settled and a stale, unknown or unoffered action settles nothing. A saved permission audit is an inert reading, and a historical prefix has no live requests to answer. @@ -6390,7 +6390,7 @@ Recovery changes nothing above it. Placement at narrow describes and mounts only the surface the route selected. The unselected surface has no node, cell, frame-map entry, focus stop or pointer target; both surface controls remain available, and application and journal -state are retained. The inspection reading is different, and is not described there at all: a +state are saved. The inspection reading is different, and is not described there at all: a narrow frame has no inspection region to put a binding row or a recorded answer in, and a row described into a region that does not exist is mounted, focusable and placed nowhere — a Tab stop drawing no cell and a pointer target behind @@ -6449,9 +6449,9 @@ entry that read a file, wrote three and stopped at a question is reopened at the printed location, those four answer from what they produced, and the write after the question happens for the first time. -Four operations are retained and no others: a `` text read, a `` +Four operations are saved and no others: a `` text read, a `` write, a ``, and the directory a `` ensures, each in the -ambient checkout the entry was started in. A read is retained because a document +ambient checkout the entry was started in. A read is saved because a document binds its text and a continuation has to offer the same binding; a glob, a path check and a temporary directory bind nothing a continuation must answer again, and are left alone. A call outside that checkout is ordinary filesystem @@ -6479,7 +6479,7 @@ checkout and takes no lease. Recording is ordinary ancestor-max `Files` middleware over the one document filesystem, so what is kept is the result the whole composition answered with, -and a generated fragment's admitted operations are retained exactly as an +and a generated fragment's admitted operations are saved exactly as an authored one's are: they cross the same route. A provider beneath that answers directly, or transforms the path or content and delegates, is provider behavior; the request as it arrived stays the operation's identity. Terminal permission @@ -6711,7 +6711,7 @@ DurableEvents -> frozen ReplModel -> resolved immutable view | --- | --- | | `model.ts` | projecting one validated Journal prefix into a frozen model | | `route.ts` | the location grammar, and resolving one against a model | -| `journal.ts` | the retained NDJSON stream and the repository over a root | +| `journal.ts` | the saved NDJSON stream and the repository over a root | | `session.ts` | admitting entries in turn, reopening one history, and the live overlay | | `entries.ts` | partitioning one physical journal into entry segments, and the private stream view over one of them | | `expansion.ts` | pausing and continuing expansion over the public seams | @@ -6719,7 +6719,7 @@ DurableEvents -> frozen ReplModel -> resolved immutable view | `description.ts` | opaque immutable descriptions and the closed action boundary | | `reconcile.ts` | reconciling descriptions into one mounted Freedom tree | | `handoff.ts` | the acknowledged commit boundary | -| `navigation.ts` | every retained position and the head, as the one reading taken at the whole file | +| `navigation.ts` | every saved position and the head, as the one reading taken at the whole file | | `history-rail.ts` | the five-row History band, placed from engine measurements | | `source-reading.ts` | one entry as output and then source, with what each element is doing | | `presentation-text.ts` · `presentation-style.ts` | what the characters of a reading are, and the colours it is drawn in | @@ -6748,7 +6748,7 @@ for what it actually does: canonical resolution, admission, configured permission, ownership, coordination, publication, execution, or definitive settlement. Two serialized spellings keep the older word because their bytes are a compatibility contract, not a description: the durable `LaunchFailureClass` -value `"directory-authority"`, which a retained launch record is parsed against +value `"directory-authority"`, which a saved launch record is parsed against by exact match, and the Files invariant category `"authority"`, which crosses loaded copies and is matched the same way. Renaming either would break replay and categorization rather than clarify them, so both stay exactly as written. @@ -6763,10 +6763,10 @@ and categorization rather than clarify them, so both stay exactly as written. | middleware | applied by the lexical structure, used by runtime execution [Reference](#two-layers). | | workflow run | a workflow being carried out with its progress and outcome recorded durably; document executions perform its work, while ongoing effects remain scoped to the document execution in which they run [Reference](#workflow-runs). | | document execution | one evaluation of a root document initiated through `execute()`, producing one output stream and one completion result while reading and appending a durable journal; its ongoing effects belong to the Effection scope in which the evaluation runs [Reference](#workflow-runs). | -| workflow definition | one closed retained source-bundle descriptor: `hashAlgorithm`, `bundleHash`, `entrypoint`, `sources`, optional `targetPath` and `components`. It carries no version, kind, Git base or pinned commit; exact target and component mapping participate in identity. [Source and identity](#identity-is-separate-from-retrieval). | -| source bundle | The exact retained entrypoint, source manifest and optional component mapping from which a workflow executes. [Owning rule](#identity-is-separate-from-retrieval). | +| workflow definition | one closed saved source-bundle descriptor: `hashAlgorithm`, `bundleHash`, `entrypoint`, `sources`, optional `targetPath` and `components`. It carries no version, kind, Git base or pinned commit; exact target and component mapping participate in identity. [Source and identity](#identity-is-separate-from-retrieval). | +| source bundle | The exact saved entrypoint, source manifest and optional component mapping from which a workflow executes. [Owning rule](#identity-is-separate-from-retrieval). | | logical path | a portable identity inside a source bundle, never a host filesystem path: NFC Unicode scalar values, `/`-separated, without leading or trailing separator, NUL, C0 control, DEL, backslash or `#`, and without an empty, `.` or `..` segment, compared case-sensitively by its UTF-8 bytes. The containing directory, the absolute path and the invocation working directory are retrieval facts and never identity [Reference](#identity-is-separate-from-retrieval). | -| workflow component bundle | the closed set of authored Markdown components a workflow root declares. A bundle maps each name onto a logical path the same source bundle retains. It is both definition identity and canonical resolution: the names it declares resolve to its exact retained sources and to nothing else [Reference](#the-workflow-lifecycle). | +| workflow component bundle | the closed set of authored Markdown components a workflow root declares. A bundle maps each name onto a logical path the same source bundle retains. It is both definition identity and canonical resolution: the names it declares resolve to its exact saved sources and to nothing else [Reference](#the-workflow-lifecycle). | | declared Markdown component | Exact first-party Markdown a trusted host declares to one execution. [Owning rule](#the-declared-markdown-boundary). | | private component closure | A declaration-owned set of implementations available only at eligible lexical occurrences of its own body. [Owning rule](#the-declared-markdown-boundary). | | retrieval metadata | replaceable, credential-free information about where a workflow definition can be fetched from now; it takes no part in run identity and is reauthorized by the host before use [Reference](#identity-is-separate-from-retrieval). | @@ -6804,23 +6804,23 @@ and categorization rather than clarify them, so both stay exactly as written. | blocker | execution needs input (auth, a human answer); not a failure [Reference](#error-propagation). | | suspension | a durable wait: a crash restarts into the same wait [Reference](#durable-suspension). | | suspension ID | an opaque stable identifier derived by the trusted workflow execution for one logical suspension request; replay preserves it and only equality and input correlation are public [Reference](#durable-suspension). | -| delivery | Retained input for one exact durable suspension, separate from resuming or publishing its answer. [Owning rule](#typed-answer-delivery). | +| delivery | Saved input for one exact durable suspension, separate from resuming or publishing its answer. [Owning rule](#typed-answer-delivery). | | answer publication | appending the durable answer event that records what ended one durable wait; performed by the resuming document execution, never by a delivery [Reference](#typed-answer-delivery). | -| answer consumption | marking a retained delivery spent; it commits in the same transaction as the answer publication it belongs to, so neither can be observed without the other [Reference](#typed-answer-delivery). | +| answer consumption | marking a saved delivery spent; it commits in the same transaction as the answer publication it belongs to, so neither can be observed without the other [Reference](#typed-answer-delivery). | | workflow executor | the live host invocation that advances or settles one workflow run [Reference](#workflow-lifecycle-ownership). | | executor lock | the scope-owned, non-blocking exclusive host lock that permits at most one workflow executor for a run [Reference](#workflow-lifecycle-ownership). | -| stale execution | an unfinished retained document execution found after acquiring the executor lock proves its previous workflow executor is gone [Reference](#workflow-lifecycle-ownership). | -| inspection recovery coordination | provider-private cross-process exclusion between copying a crashed run's database-and-journal pair for inspection and opening the retained database write-capably for SQLite recovery; it grants no executor or lifecycle ownership [Reference](#read-only-lifecycle-inspection). | -| recovered inspection snapshot | an immutable lifecycle snapshot read from a private scratch copy after SQLite rolls that copy's hot journal back to the retained store's last committed state; the retained database and journal are not changed [Reference](#read-only-lifecycle-inspection). | -| XMD artifact | one immutable portable evidence file with the `.xmd` extension, containing one workflow run's committed retained state at one artifact frontier together with its workflow definition source closure; it is neither a live run store nor permission to continue the source run [Reference](#portable-xmd-artifacts). | +| stale execution | an unfinished saved document execution found after acquiring the executor lock proves its previous workflow executor is gone [Reference](#workflow-lifecycle-ownership). | +| inspection recovery coordination | provider-private cross-process exclusion between copying a crashed run's database-and-journal pair for inspection and opening the saved database write-capably for SQLite recovery; it grants no executor or lifecycle ownership [Reference](#read-only-lifecycle-inspection). | +| recovered inspection snapshot | an immutable lifecycle snapshot read from a private scratch copy after SQLite rolls that copy's hot journal back to the saved store's last committed state; the saved database and journal are not changed [Reference](#read-only-lifecycle-inspection). | +| XMD artifact | one immutable portable evidence file with the `.xmd` extension, containing one workflow run's committed saved state at one artifact frontier together with its workflow definition source closure; it is neither a live run store nor permission to continue the source run [Reference](#portable-xmd-artifacts). | | artifact frontier | the exact committed lifecycle snapshot, journal boundary and current Workspace root an XMD artifact records [Reference](#portable-xmd-artifacts). | -| workflow definition source closure | the exact root Markdown source and every source in its declared workflow component bundle, the retained bytes re-derived from the run's own store. Retrieval metadata, provenance and a repository checkout are not part of either [Reference](#portable-xmd-artifacts). | -| artifact manifest | the canonical versioned inventory of every semantic record and retained byte in one XMD artifact, excluding its own derived identity and physical container layout [Reference](#portable-xmd-artifacts). | -| XMD artifact identity | the lowercase SHA-256 of an XMD artifact's artifact manifest; it identifies the evidence independently of its host path and physical encoding and is retained as the source identity of a history fork made from that artifact [Reference](#portable-xmd-artifacts). | +| workflow definition source closure | the exact root Markdown source and every source in its declared workflow component bundle, the saved bytes re-derived from the run's own store. Retrieval metadata, provenance and a repository checkout are not part of either [Reference](#portable-xmd-artifacts). | +| artifact manifest | the canonical versioned inventory of every semantic record and saved byte in one XMD artifact, excluding its own derived identity and physical container layout [Reference](#portable-xmd-artifacts). | +| XMD artifact identity | the lowercase SHA-256 of an XMD artifact's artifact manifest; it identifies the evidence independently of its host path and physical encoding and is saved as the source identity of a history fork made from that artifact [Reference](#portable-xmd-artifacts). | | Agent session portability evidence | Verified portable session checkpoint tokens and bundle bytes, or an explicit unavailability classification. [Owning rule](#portable-xmd-artifacts). | -| provider checkpoint token | one opaque value a provider issued for one completed Agent Prompt, retained in Agent session portability evidence beside the event it belongs to. It says where a conversation reached; it is not a credential, is never derived from event position, transcript text, another token or a provider head, and no ordinary presentation renders it [Reference](#portable-xmd-artifacts). | -| Agent session bundle | the opaque bytes a provider captured one Agent session as, retained under that session's key in an XMD artifact. Nothing scans or scrubs them: a bundle may hold transcript text, tool state, secrets a conversation repeated and the exporting host's own paths, and it carries the confidentiality of the whole retained Workspace [Reference](#portable-xmd-artifacts). | -| Workspace | the provider-neutral, run-owned environment that supplies retained filesystem, repository, process and working-directory capabilities to a workflow [Reference](#workflow-workspace). | +| provider checkpoint token | one opaque value a provider issued for one completed Agent Prompt, saved in Agent session portability evidence beside the event it belongs to. It says where a conversation reached; it is not a credential, is never derived from event position, transcript text, another token or a provider head, and no ordinary presentation renders it [Reference](#portable-xmd-artifacts). | +| Agent session bundle | the opaque bytes a provider captured one Agent session as, saved under that session's key in an XMD artifact. Nothing scans or scrubs them: a bundle may hold transcript text, tool state, secrets a conversation repeated and the exporting host's own paths, and it carries the confidentiality of the whole saved Workspace [Reference](#portable-xmd-artifacts). | +| Workspace | the provider-neutral, run-owned environment that supplies saved filesystem, repository, process and working-directory capabilities to a workflow [Reference](#workflow-workspace). | | document filesystem | the files a document names in its own text, reached only through `API.Files`; distinct from the host paths the engine's own control plane reads [Reference](#the-document-filesystem-boundary). | | Files provider | the installed implementation of `API.Files` for a document execution: the host provider under `xmd run`, a transaction-bound provider under a workflow run. There is no default, and no provider falls back to another [Reference](#the-document-filesystem-boundary). | | stable host namespace | the condition the host Files provider's containment is stated under: no other process replaces a directory, symlink, junction or reparse point between the moment a path is observed and the moment it is used [Reference](#the-document-filesystem-boundary). | @@ -6830,8 +6830,8 @@ and categorization rather than clarify them, so both stay exactly as written. | attached service | a scoped host process that publishes its authenticated loopback endpoint through the XMD service handshake protocol and remains supervised for the lifetime of its service attachment [Reference](#replay-safe-live-services). | | effect transaction | the single atomic SQLite transaction that publishes one Workspace-local mutation together with that effect's journal result [Reference](#replay-and-effect-transactions). | | external effect | an effect whose provider-owned outcome cannot participate in the Workspace SQLite transaction and therefore requires a stable identity and provider reconciliation [Reference](#replay-and-effect-transactions). | -| XMD-mediated HTTP read | one bounded, non-mutating HTTP request an authored document performs through the contextual Fetch capability, whose detached response is retained as an ordinary durable observation. It is distinct from an Agent's own network permission, which it does not grant, and from a reconciled mutating external effect: it performs no reconciliation, so an interruption before its record commits may repeat the read [Reference](#replay-and-effect-transactions). | -| Git-host evidence read | A complete bounded observation of one canonical pull-request URL, retained as an ordinary durable effect. [Owning rule](#git-host-evidence-reads). | +| XMD-mediated HTTP read | one bounded, non-mutating HTTP request an authored document performs through the contextual Fetch capability, whose detached response is saved as an ordinary durable observation. It is distinct from an Agent's own network permission, which it does not grant, and from a reconciled mutating external effect: it performs no reconciliation, so an interruption before its record commits may repeat the read [Reference](#replay-and-effect-transactions). | +| Git-host evidence read | A complete bounded observation of one canonical pull-request URL, saved as an ordinary durable effect. [Owning rule](#git-host-evidence-reads). | | object-source attachment | Invocation-private authenticated local Git objects and destination supplied to one reconciliation. [Owning rule](#replay-and-effect-transactions). | | checkpoint | a completed journal boundary associated with the logical Workspace root visible after that effect [Reference](#replay-and-effect-transactions). | | history fork | a new workflow run that replays a compatible journal prefix and continues from its checkpoint and Workspace root under a new immutable document definition [Reference](#replay-and-effect-transactions). | @@ -6858,7 +6858,7 @@ and categorization rather than clarify them, so both stay exactly as written. | client-native attachment | ACP attachment to the exact conversation constructed by a native process [Full contract](#native-agent-sessions). | | identity provenance | Who chose the provider-native identity [Full contract](#native-agent-sessions). | | quiescence | Acknowledgement that nothing the former owner started can still act [Full contract](#native-agent-sessions). | -| recovery tombstone | Retained active ownership that a crash alone cannot clear [Full contract](#native-agent-sessions). | +| recovery tombstone | Saved active ownership that a crash alone cannot clear [Full contract](#native-agent-sessions). | | provider partition | One independently owned provider runtime, store, session set and teardown [Full contract](#native-agent-sessions). | | `JournalProvenance` | a non-operational, equality-only witness that a live publication stream descends from the exact journal backend a provider selected for one workflow run; it grants no append, read, execution, publication or reconciliation capability, and is meaningful only because the provider retains the witness it established and later requires exact equality [Reference](#the-weak-journal-provenance-association). | | result object | the value a component binds instead of failing: `{ok: true, value}` or `{ok: false, …}`, whose failure members the component declares [Reference](#construct-inventory). | @@ -6880,7 +6880,7 @@ Status is measured against main. | Construct | Does | Status | | --- | --- | --- | | `xmd syntax` | Describes the production run-profile vocabulary without execution, as deterministic Markdown or version-2 JSON. [Construction and observation](#the-syntax-symbols-boundary); [named documentation](#the-canonical-protected-tier). | Built. | -| `` | Renders the lexical vocabulary or requested component documentation under canonical protection. [Forms, capture, documentation and retained reads](#the-canonical-protected-tier). | Built; evaluation narrows the lexical execution reference. | +| `` | Renders the lexical vocabulary or requested component documentation under canonical protection. [Forms, capture, documentation and saved reads](#the-canonical-protected-tier). | Built; evaluation narrows the lexical execution reference. | | document validation | validates one supplied root projection and the recursive Markdown source closure normal component selection discovers, returning deterministic version-1 document diagnostics and `valid`, `invalid` or `not-statically-checkable` invocation outcomes without evaluating document code or installing operational host behavior [Invariant](#the-document-validation-boundary). | built on the #654 stack | | `xmd plan` | Produces exact approved Plan source through the packaged adapter and shared Plan component, then delivers it to one exclusive sink without running it. [Command grammar](specs/plan-command-spec.md#command-grammar), [profile](specs/plan-command-spec.md#the-plan-writer-profile), [progress](specs/plan-command-spec.md#watching-a-plan-being-written), [journal](specs/plan-command-spec.md#the---journal-file) and [final admission](specs/plan-command-spec.md#structural-host-validation-and-the-artifact). | Built. | | `` | Authors one exact-source Plan inside the enclosing execution, using the same fixed writer profile as the command. [Packaged component](specs/plan-command-spec.md#the-packaged-plan-component) and [language contract](specs/executable-mdx-spec.md#53-import-durableimportcomponent). | Built; no delivery, custom root, policy prop or replacement selector (#536). | @@ -6900,7 +6900,7 @@ Status is measured against main. | `inspectSource()` | Locates executable delimiters using the engine scanner without interpreting source. [Source inspection](#1b-where-a-texts-elements-are-without-running-it). | built on this stack | | document targets | catalogs a root document's addressable static headings, resolves one selector to one exact target, and projects the document to it before expansion [Invariant](#document-targets). | built on the #412 stack | | document-aware `xmd run … --help` | describes what one document declares and every target it addresses, each as a full document reference with the description its section states, by inspection alone [Interface and evidence](specs/executable-mdx-spec.md#document-targets). | built on the #463 stack | -| standard-input root documents | Acquires one whole source document from standard input only for explicit `xmd run` with the exact `-` document argument. [Root grammar, cancellation and retained origin](#the-root-document-a-run-is-given). | Built. | +| standard-input root documents | Acquires one whole source document from standard input only for explicit `xmd run` with the exact `-` document argument. [Root grammar, cancellation and saved origin](#the-root-document-a-run-is-given). | Built. | | targeted `xmd run` | reads a file argument as a document reference and executes the one exact target its selector resolved to, replacing the selector before execution rereads the file [Invariant](#document-targets). | built on the #412 stack | | targeted workflow definition | a workflow definition optionally carries the exact canonical document target, which takes part in definition identity and in compatible reuse but not in the source-bundle hash [Invariant](#document-targets); [Interface and evidence](specs/workflow-spec.md#the-document-target-a-run-is-a-run-of). | built on the #412 stack; `xmd workflow start` takes a document reference and resolves any selector through core against the bytes it is about to retain | | source-bundle workflow definition | retains exact source bytes under logical paths, with domain-separated source hashes, lengths, bundle hash and optional exact target/component mapping. [Definition identity](#identity-is-separate-from-retrieval) and [source retention](#what-a-run-retains). | Built; one descriptor and live schema version 1, with no migration. Artifact-backed fork remains unbuilt. | @@ -6908,14 +6908,14 @@ Status is measured against main. | declared Markdown component | Reserves exact first-party Markdown and its lexical private closure for one execution. [Capture, import and replay](#the-declared-markdown-boundary). | Built; packaged Plan and the review graph use the declaration boundary. | | workflow component bundle | Retains a closed mapping of component names to the definition source bundle. [Workflow lifecycle](#the-workflow-lifecycle). | built on the #301 stack; the full adversarial implementation loop and its scheduling remain unbuilt (#300), and generated XMD admits no bundled Markdown component (#369) | | `getWorkflowRun()` | reads the run the current execution is associated with [Invariant](#workflow-runs). | built on the #366 stack | -| `retainedWorkflowInstallation()` | associates one document execution with a run storage already created, requiring exact journal agreement. It resolves no revision and names no Git feature, and a run retains the bytes it executes, so nothing afterwards reaches a repository for its source [Invariant](#workflow-runs); [Interface and evidence](specs/workflow-spec.md#3-where-the-run-is-prepared). | built on the #366 stack | +| `savedWorkflowInstallation()` | associates one document execution with a run storage already created, requiring exact journal agreement. It resolves no revision and names no Git feature, and a run retains the bytes it executes, so nothing afterwards reaches a repository for its source [Invariant](#workflow-runs); [Interface and evidence](specs/workflow-spec.md#3-where-the-run-is-prepared). | built on the #366 stack | | `GitQuery.resolve()` | verifies and resolves one Git revision expression contextually [Interface and evidence](specs/workflow-spec.md#7-the-git-capability--moved). | built on main | -| workflow run storage | creates or compatibly finds one run by public run ID, retains its identity, state, document executions and filtered journal, and validates immutable Workspace roots through one provider-owned connection entry [Invariant](#workflow-run-storage); [Interface and evidence](specs/workflow-spec.md#9-retained-run-storage). | built on the #365 stack; the CLI lifecycle reaches it on the #366 stack | +| workflow run storage | creates or compatibly finds one run by public run ID, retains its identity, state, document executions and filtered journal, and validates immutable Workspace roots through one provider-owned connection entry [Invariant](#workflow-run-storage); [Interface and evidence](specs/workflow-spec.md#9-saved-run-storage). | built on the #365 stack; the CLI lifecycle reaches it on the #366 stack | | caller-owned storage transaction | publishes several changes, including journal events, in one transaction nothing else enlists in [Invariant](#one-database-at-a-time-and-a-transaction-a-caller-can-hold). | built on main | -| REPL entry retained file work | one executing REPL entry's own recording, established by core initialization in the scope the invocation owns and ending with it: it retains the `` reads, `` writes, `` deletions and `` ensures that land inside the ambient checkout the entry was started in, authored and admitted-generated alike, because both cross the one document filesystem. A completed operation answers from its saved result without live path canonicalization, ambient discovery, a lease, a permission lookup or a filesystem call — so only *new* work needs the environment, a continuation still binds the text its read answered with while the file says something else, and a new entry reading that path sees what is there now. A record's name is the digest of the record version, the operation kind and the complete received request, so two equal requests in one entry are one name at two ordered positions and the durable engine's positional matching tells them apart; equal work in two entries is the same name, and each entry executing against its own stream segment is what keeps those apart. Recording is ordinary ancestor-max `Files` middleware, so a provider beneath that answers directly or transforms and delegates is kept as provider behavior while the received request stays the operation's identity. A shared Git directory admits one live *mutating* entry at a time, held lazily for as long as the execution lasts and refused rather than queued; no read acquires it, so a new read proceeds while another entry holds it. There is no managed checkout, no `` or `` selection, no local Git operation and nothing external here; work outside the checkout stays ordinary and unrecorded, and a passive reading initializes nothing [Reference](#what-one-entry-retains-about-the-checkout-it-is-standing-in). | built on the #181 stack | +| REPL entry saved file work | one executing REPL entry's own recording, established by core initialization in the scope the invocation owns and ending with it: it retains the `` reads, `` writes, `` deletions and `` ensures that land inside the ambient checkout the entry was started in, authored and admitted-generated alike, because both cross the one document filesystem. A completed operation answers from its saved result without live path canonicalization, ambient discovery, a lease, a permission lookup or a filesystem call — so only *new* work needs the environment, a continuation still binds the text its read answered with while the file says something else, and a new entry reading that path sees what is there now. A record's name is the digest of the record version, the operation kind and the complete received request, so two equal requests in one entry are one name at two ordered positions and the durable engine's positional matching tells them apart; equal work in two entries is the same name, and each entry executing against its own stream segment is what keeps those apart. Recording is ordinary ancestor-max `Files` middleware, so a provider beneath that answers directly or transforms and delegates is kept as provider behavior while the received request stays the operation's identity. A shared Git directory admits one live *mutating* entry at a time, held lazily for as long as the execution lasts and refused rather than queued; no read acquires it, so a new read proceeds while another entry holds it. There is no managed checkout, no `` or `` selection, no local Git operation and nothing external here; work outside the checkout stays ordinary and unrecorded, and a passive reading initializes nothing [Reference](#what-one-entry-retains-about-the-checkout-it-is-standing-in). | built on the #181 stack | | live durable-operation coordinator | explicitly coordinates structured live execution with existing Yield publication while leaving replay and callback effects unchanged [Invariant](#live-effect-coordination). | built on the #365 stack | | Workspace coordination API | fails closed by default; replaceable context routes only a one-use provider selection, while the selected provider directly invokes an execution-owned credentialed capability for execution, publication and failure activation [Invariant](#live-effect-coordination). | built on the #365 stack; the Deno provider installs an adapter-private atomic handler | -| shared Git-host reconciliation | Reconciles Git push and pull-request upsert through one request-only boundary whose private terminal alone settles the retained outcome. [Invariant](#external-effects-and-git-host-mutations); [Invariant](#issue-effects). | Built on #297; Push (#370) and PullRequest (#295) consume it. Issue (#296) has its own middleware boundary. | +| shared Git-host reconciliation | Reconciles Git push and pull-request upsert through one request-only boundary whose private terminal alone settles the saved outcome. [Invariant](#external-effects-and-git-host-mutations); [Invariant](#issue-effects). | Built on #297; Push (#370) and PullRequest (#295) consume it. Issue (#296) has its own middleware boundary. | | explicit WorkflowRun journal route | binds one already-filtered publication to one exact active transaction and otherwise uses ordinary serialized journal storage [Invariant](#local-workspace-topology). | built on the #365 stack | | `API.Service` / `startService()` | creates an authenticated, supervised loopback service attachment through a provider-neutral operation [Invariant](#replay-safe-live-services); [Interface and evidence](specs/executable-mdx-spec.md#attached-service-api). | built on main | | foreground command routing and retention | forwards a child's channels live, captures stdout for a `` region, and retains output only when the host asked for a record [Invariant](#foreground-commands). | built on the #441 stack | @@ -6935,9 +6935,9 @@ Status is measured against main. | `useWorkflowServiceDenial()` | provides a non-delegating workflow service denial provider, installed inside every start and resume execution scope [Invariant](#replay-safe-live-services). | built on the #366 stack | | `xmd workflow start` / `xmd workflow resume` | starts or resumes a workflow run from the CLI, under the Deno entrypoints only [Invariant](#the-workflow-lifecycle); [Interface and evidence](specs/workflow-workspace-spec.md#3-workflow-lifecycle). | built on the #366 stack; both acquire #367's executor lock before any lifecycle transition | | implicit workflow Workspace | retains provider-neutral filesystem, repository and attachment state by run ID [Invariant](#workflow-workspace); [Interface and evidence](specs/workflow-workspace-spec.md#5-workspace-structure-and-identity). | document filesystem built on the #366 stack and Repository/Worktree composition on the #293 stack; process capabilities unbuilt (#218) | -| `` / `` composition under a workflow run | names a Git repository and its linked checkouts inside the run-owned Workspace, installs each as contextual working directory, and retains creation identity beside the retained Git bytes [Interface and evidence](specs/workflow-workspace-spec.md#6-repository-and-worktree). | built on the #293 stack, Deno provider only | +| `` / `` composition under a workflow run | names a Git repository and its linked checkouts inside the run-owned Workspace, installs each as contextual working directory, and retains creation identity beside the saved Git bytes [Interface and evidence](specs/workflow-workspace-spec.md#6-repository-and-worktree). | built on the #293 stack, Deno provider only | | `` | Creates or adopts its target before content, scopes cwd without changing Repository selection, and restores cwd without deleting created directories. [Invariant](#the-document-filesystem-boundary); [Interface and evidence](specs/executable-mdx-spec.md#6132-entering-a-directory-dir). | Directory-creation contract added by #643; host and workflow Files providers. | -| transactional Git effects (`Git.Switch` / `Git.Add` / `Git.Commit`) under a workflow run | publish local Git mutations with their journal result; the enclosing Repository and the contextual working directory select which retained checkout one runs in, and neither observation admits anything — the observed record is compared with the retained row and the directory with the checkouts that row holds, so a failure of admission, of retained state or of an unrecognized native condition fails the run instead of publishing a result [Invariant](#workspace-local-transactions); [Interface and evidence](specs/workflow-workspace-spec.md#7-git-operations). | built on the #294 stack, Deno provider only | +| transactional Git effects (`Git.Switch` / `Git.Add` / `Git.Commit`) under a workflow run | publish local Git mutations with their journal result; the enclosing Repository and the contextual working directory select which saved checkout one runs in, and neither observation admits anything — the observed record is compared with the saved row and the directory with the checkouts that row holds, so a failure of admission, of saved state or of an unrecognized native condition fails the run instead of publishing a result [Invariant](#workspace-local-transactions); [Interface and evidence](specs/workflow-workspace-spec.md#7-git-operations). | built on the #294 stack, Deno provider only | | `Git.Push` under a workflow run | Publishes or adopts the selected named branch through authenticated non-force reconciliation. [Authored contract](specs/workflow-workspace-spec.md#74-push) and [effect mechanics](#replay-and-effect-transactions). | built on the #370 stack, Deno provider only | | `` under a workflow run | Upserts the exact published branch and returns stable evidence only after this run proves matching Push history. [Props, results and refusals](specs/workflow-workspace-spec.md#75-pull-request); [positive-integer number and reconciliation](#issue-effects). | built on the #295 stack, Deno provider only | | repository composition vocabulary | Declares twelve ordinary shadowable defaults without installing an operational provider. [Names and profile ownership](#ordinary-repository-vocabulary). | built on the #643 stack | @@ -6947,22 +6947,22 @@ Status is measured against main. | `` and its evidence reads under an ordinary run | Reads current evidence or upserts only after this invocation proved the exact matching Push [Full contract](#ordinary-repository-ownership). | built on the #643 stack, Deno and compiled only | | `` under an ordinary run | Calls the configured Issue provider using this execution and expansion identity [Full contract](#ordinary-repository-ownership). | built on the #643 stack, Deno and compiled only | | ordinary repository provider assembly | Assembles a fresh provider per ordinary execution, acquiring each resource only when needed [Full contract](#ordinary-repository-ownership). | built on the #643 stack | -| `` under a workflow run | Reads or upserts an Issue through its independent middleware boundary. [Forms and retained results](specs/workflow-workspace-spec.md#103-issue-effects); [tracker normalization, routing and provider responsibilities](#issue-effects). | built on the #296 stack; GitHub middleware, Deno host | -| workflow lifecycle inspection and control | reads status/list/history without advancing a run, recovering a private copy when a crashed source needs rollback; enforces the executor lock, refuses live cancellation, cancels non-live runs under that lock and deletes retained state [Invariant](#read-only-lifecycle-inspection); [Invariant](#cancellation-control). | direct read-only inspection and control built on the #367 stack; coordinated recovered inspection built on the #513 stack, Deno provider only | +| `` under a workflow run | Reads or upserts an Issue through its independent middleware boundary. [Forms and saved results](specs/workflow-workspace-spec.md#103-issue-effects); [tracker normalization, routing and provider responsibilities](#issue-effects). | built on the #296 stack; GitHub middleware, Deno host | +| workflow lifecycle inspection and control | reads status/list/history without advancing a run, recovering a private copy when a crashed source needs rollback; enforces the executor lock, refuses live cancellation, cancels non-live runs under that lock and deletes saved state [Invariant](#read-only-lifecycle-inspection); [Invariant](#cancellation-control). | direct read-only inspection and control built on the #367 stack; coordinated recovered inspection built on the #513 stack, Deno provider only | | XMD artifact export, inspection and fork source | Seals one committed frontier into immutable version-1 evidence for read-only inspection and future history-fork input. [Artifact identity and compatibility](#portable-xmd-artifacts) and [inspection APIs](#workflow-lifecycle-ownership). | Export, total verification, status and history built for Deno; artifact-source fork remains unbuilt. | -| Agent session portability evidence in an XMD artifact | classifies every logical Agent session that contributed a retained Prompt as portable — with ordered provider checkpoint tokens and an opaque Agent session bundle — or as explicitly unavailable, as two content kinds inside the existing version-1 manifest and identity [Invariant](#portable-xmd-artifacts); [Interface and evidence](specs/xmd-artifact-spec.md#25-agent-session-portability-evidence). | specified by `specs/xmd-artifact-spec.md` §2.5; the closed union, both content kinds and the complete post-identity profile verifier are built on the #621 stack, Deno provider only. Provider bundle capture, Agent-aware export, intrinsic Agent-aware inspection and artifact-backed fork are unbuilt | +| Agent session portability evidence in an XMD artifact | classifies every logical Agent session that contributed a saved Prompt as portable — with ordered provider checkpoint tokens and an opaque Agent session bundle — or as explicitly unavailable, as two content kinds inside the existing version-1 manifest and identity [Invariant](#portable-xmd-artifacts); [Interface and evidence](specs/xmd-artifact-spec.md#25-agent-session-portability-evidence). | specified by `specs/xmd-artifact-spec.md` §2.5; the closed union, both content kinds and the complete post-identity profile verifier are built on the #621 stack, Deno provider only. Provider bundle capture, Agent-aware export, intrinsic Agent-aware inspection and artifact-backed fork are unbuilt | | historical authored source | retains an authored durable operation's normalized `SourcePosition` beside its identity, and history parses it or refuses the entry [Invariant](#read-only-lifecycle-inspection); [Interface and evidence](specs/workflow-workspace-spec.md#42-history). | built on the #367 stack | -| history fork | creates a new run from one compatible checkpoint and retained Workspace root, under a new immutable definition and normalized props [Invariant](#retained-effects-and-history-forks); [Interface and evidence](specs/workflow-workspace-spec.md#11-history-forks). | built on the #368 stack, Deno provider only | +| history fork | creates a new run from one compatible checkpoint and saved Workspace root, under a new immutable definition and normalized props [Invariant](#saved-effects-and-history-forks); [Interface and evidence](specs/workflow-workspace-spec.md#11-history-forks). | built on the #368 stack, Deno provider only | | workflow Agent session | Uses inert placement and authenticated first-turn mapping under the run-owned no-native-tool profile. [Session mapping](#agent-permissions-and-generated-xmd). | built on the #302 stack, with the explicit ACP-only selection from #561; the portable proof that an adapter honours an empty tool set is tracked by #496 and does not widen the ceiling | | generated-XMD admission | Preflights and retains one generated fragment under the exact selected identities and forms. [Admission and continuation](#agent-permissions-and-generated-xmd). | built on the #369 stack, continuation basis amended by #589, and the host's contribution narrowed to `Component.generated` by this stack; core owns the mechanics and no host wrapper remains | | `` | Runs untrusted fragment text or paired-produced text under the host's captured generated selection; whole-fragment admission, local data expressions and ordinary rendered output remain distinct from provider permission. [Admission and captured implementations](#agent-permissions-and-generated-xmd). | built on the #302 stack, extended by #369, and narrowed to a name-and-form selection by this stack | | generated mutation proposals | Admits filesystem change proposals through the host's generated selection and ordinary component transactions. [Invariant](#agent-permissions-and-generated-xmd); [Interface and evidence](specs/workflow-workspace-spec.md#mutation-proposal-admission). | Built on #369/#567, with persistent directory creation added by #643; legacy Dir grants refuse, and Git/process/credential/external-write classes remain excluded. | -| Deno-local DOFS provider | owns one authoritative SQLite/DOFS connection per run path, captures arbitrary canonical retained roots, privately restores them, and atomically coordinates one Workspace mutation with its filtered Yield [Invariant](#local-workspace-topology). | built on the #365 stack; public document filesystem effects and the CLI lifecycle route to it on the #366 stack | +| Deno-local DOFS provider | owns one authoritative SQLite/DOFS connection per run path, captures arbitrary canonical saved roots, privately restores them, and atomically coordinates one Workspace mutation with its filtered Yield [Invariant](#local-workspace-topology). | built on the #365 stack; public document filesystem effects and the CLI lifecycle route to it on the #366 stack | | scoped Worker Shell | executes `just-bash` through the Workspace adapter inside a Deno Worker [Interface and evidence](specs/workflow-workspace-spec.md#104-worker-shell). | containment and effect-transaction POCs complete (#351, #357); production integration unbuilt | | `` | retry a region until it completes [Invariant](#1-middleware-wraps-the-work). | defined, unbuilt | | `suspendFor()` | publishes one filtered suspension request, settles `suspended`, releases the executor lock, and re-enters the same wait without duplicating it [Invariant](#durable-suspension). | built on the #367 stack | -| `xmd workflow answer ` | retains one schema-validated value for one retained wait, taking no executor lock and changing no run state [Invariant](#typed-answer-delivery); [Interface and evidence](specs/workflow-workspace-spec.md#typed-answer-delivery). | built by #300 | -| `suspension_answer` durable effect | ends a wait from retained delivery state, publishing the answer and consuming that state in one transaction [Invariant](#typed-answer-delivery). | built by #300 | +| `xmd workflow answer ` | retains one schema-validated value for one saved wait, taking no executor lock and changing no run state [Invariant](#typed-answer-delivery); [Interface and evidence](specs/workflow-workspace-spec.md#typed-answer-delivery). | built by #300 | +| `suspension_answer` durable effect | ends a wait from saved delivery state, publishing the answer and consuming that state in one transaction [Invariant](#typed-answer-delivery). | built by #300 | | `` · `` · `` | read the reviews, comments and checks a Git host already holds for one numbered pull request, completely or not at all [Invariant](#git-host-evidence-reads). | built by #576 | | workflow scheduling (watchers, unattended iteration, remote host selection) | — [Invariant](#typed-answer-delivery). | #300 | | `` | binds `{ok: true, value}` or `{ok: false, error}`; a failure becomes a bound value, not a raise [Invariant](#6-an-error-is-never-a-value). | defined, unbuilt | diff --git a/packages/acp/mod.ts b/packages/acp/mod.ts index 359ab30f2..d25cd4608 100644 --- a/packages/acp/mod.ts +++ b/packages/acp/mod.ts @@ -81,8 +81,8 @@ export type { * ACPX's persistent session store, for a host arranging its own retention. The * provider takes one; this is how a host builds and reads the one it takes. */ -export { createAcpxSessionStore, retainedSession } from "./src/session-store.ts"; -export type { AcpxRetainedSession, AcpxSessionStore } from "./src/session-store.ts"; +export { createAcpxSessionStore, savedSession } from "./src/session-store.ts"; +export type { AcpxSavedSession, AcpxSessionStore } from "./src/session-store.ts"; export { AcpxPartitionError, diff --git a/packages/acp/src/ClaudeNativeToAcp.test.md b/packages/acp/src/ClaudeNativeToAcp.test.md index b994fac5c..96c100905 100644 --- a/packages/acp/src/ClaudeNativeToAcp.test.md +++ b/packages/acp/src/ClaudeNativeToAcp.test.md @@ -302,7 +302,7 @@ the launch, and nothing after it named a second. The route, the journal, the ACP client's own arrangement and the identity the launch reported all name the same conversation — and the build this run would -use is the build the route retained. +use is the build the route saved. diff --git a/packages/acp/src/CodexNativeLaunch.test.md b/packages/acp/src/CodexNativeLaunch.test.md index fee0096e7..35dea377e 100644 --- a/packages/acp/src/CodexNativeLaunch.test.md +++ b/packages/acp/src/CodexNativeLaunch.test.md @@ -333,8 +333,8 @@ completion summary appears before the native UI opens. ## The turn was the exact one the contract names Its version, its bytes and its request identity are fixed by the product, not -composed at the call site, and the retained preparation named the same request -the retained turn later reported against. +composed at the call site, and the saved preparation named the same request +the saved turn later reported against. diff --git a/packages/acp/src/CodexZeroNativeTurnExit.test.md b/packages/acp/src/CodexZeroNativeTurnExit.test.md index 73b706bac..ae9cea962 100644 --- a/packages/acp/src/CodexZeroNativeTurnExit.test.md +++ b/packages/acp/src/CodexZeroNativeTurnExit.test.md @@ -308,8 +308,8 @@ completion summary appears before the native UI opens. ## The turn was the exact one the contract names Its version, its bytes and its request identity are fixed by the product, not -composed at the call site, and the retained preparation named the same request -the retained turn later reported against. +composed at the call site, and the saved preparation named the same request +the saved turn later reported against. diff --git a/packages/acp/src/adapter-snapshots.ts b/packages/acp/src/adapter-snapshots.ts index 933b1b86f..da886f76e 100644 --- a/packages/acp/src/adapter-snapshots.ts +++ b/packages/acp/src/adapter-snapshots.ts @@ -5,7 +5,7 @@ * and compiled distributions execute identical bytes. * * A snapshot materializes under its SHA-256. Durable identity is provider, package, - * version and digest; the quoted host-local launch path is never retained in a + * version and digest; the quoted host-local launch path is never saved in a * session or sealed artifact. * * Stage privately, publish by one rename and verify the published tree before @@ -136,7 +136,7 @@ export interface EmbeddedAdapters { * What this adapter *is*, independent of where this host put it. * * The durable half: provider, package, version and snapshot digest, and - * nothing about this machine. A retained Agent session records this and a + * nothing about this machine. A saved Agent session records this and a * sealed artifact carries it, so the same snapshot compares equal wherever it * was materialized. */ @@ -146,13 +146,13 @@ export interface EmbeddedAdapters { * * The path on its own, for a caller that needs to inspect or verify it rather * than launch it. Host-local like {@link EmbeddedAdapters.command}, and - * retained nowhere. + * saved nowhere. */ executablePath(provider: string): string; /** * The command that launches one provider's embedded adapter here. * - * The host-local half, and never retained. Answers before the adapter is on + * The host-local half, and never saved. Answers before the adapter is on * disk, because the path is the snapshot's digest beneath a known root. The * path is quoted: a materialization root may contain a space, and an unquoted * command would split there. @@ -640,7 +640,7 @@ export function createEmbeddedAdapters( * A closed registry was the wrong shape for the same reason. It made "this * build has no Codex snapshot problem to solve for Gemini" into "this build * refuses Gemini", which is a much broader restriction than the one being made, - * and it would have changed the retained identity of agents the checkpoint work + * and it would have changed the saved identity of agents the checkpoint work * has nothing to say about. * * The overlay is *qualified*: an embedded provider never falls through. A diff --git a/packages/acp/src/checkpoint.ts b/packages/acp/src/checkpoint.ts index 3ed5d85c3..38a71e483 100644 --- a/packages/acp/src/checkpoint.ts +++ b/packages/acp/src/checkpoint.ts @@ -19,7 +19,7 @@ interface RecognizedNamespace { readonly namespace: string; /** The member inside it that holds the identity. */ readonly member: string; - /** How this provider names itself in a retained checkpoint. */ + /** How this provider names itself in a saved checkpoint. */ readonly provider: string; /** What kind of identity the value is. */ readonly kind: string; diff --git a/packages/acp/src/native-launch.ts b/packages/acp/src/native-launch.ts index c6fc5869d..c6b471212 100644 --- a/packages/acp/src/native-launch.ts +++ b/packages/acp/src/native-launch.ts @@ -30,7 +30,7 @@ import type { IdentityProvenance } from "@executablemd/core"; * same build. None of it reaches a document. */ export interface NativeBinding { - /** The command whose build is observed, bound and retained. */ + /** The command whose build is observed, bound and saved. */ command: string; /** The arguments that ask that exact file its version. */ versionArgs?: readonly string[]; @@ -71,10 +71,10 @@ export interface NativeBinding { /** * Claude reports `2.1.241 (Claude Code)`. * - * The whole line is retained rather than the number alone, because the number + * The whole line is saved rather than the number alone, because the number * alone is not a build: the same version string from a different product would * compare equal. Anything that does not look like this is unrecognized, and an - * unrecognized build is refused rather than retained under a guess. + * unrecognized build is refused rather than saved under a guess. * * Exactly one line may match. Zero is an output this adapter does not * recognize; two or more is output it cannot read as one answer, and taking @@ -96,10 +96,10 @@ function claudeVersion(output: string): string | undefined { * * What is admitted is that shape, not that release: another canonical version * passes here. Exactness belongs to the session instead — the string accepted - * here becomes its retained executable binding, and a later build that differs + * here becomes its saved executable binding, and a later build that differs * refuses against that binding rather than against a constant written down here. * - * The product word is retained with the number for the same reason Claude's + * The product word is saved with the number for the same reason Claude's * whole line is: a bare semver from another tool compares equal. The one-line * rule is the same as well, and so is the silence about what did not match. */ diff --git a/packages/acp/src/provider.ts b/packages/acp/src/provider.ts index 63d7230a4..aa2bc8579 100644 --- a/packages/acp/src/provider.ts +++ b/packages/acp/src/provider.ts @@ -199,7 +199,7 @@ export interface AcpxSessionIdentity { * Absent keeps the ordinary walk from the caller's directory toward the Git * repository root. A host that keys a session by something other than a path — * a workflow run, say — answers here instead, which is also where it refuses a - * retained session it cannot continue: before ACPX is contacted, and therefore + * saved session it cannot continue: before ACPX is contacted, and therefore * before a turn could start against a replacement. * * Supplied directly by the host that built it, like the coordinator beside it: @@ -269,7 +269,7 @@ export interface AcpxProviderDependencies { * * Supplied directly by the host that built it, like the coordinator and the * route store beside it. It is deliberately not a contextual Api: executable - * validation decides which retained history may be accepted, and a decision + * validation decides which saved history may be accepted, and a decision * document middleware could replace could point the observation at one binary * while the run spawns another. * @@ -420,7 +420,7 @@ interface DetachedSession { /** * The exact value `session()` issued for this placement. * - * Retained rather than rebuilt, and compared by identity rather than by + * Saved rather than rebuilt, and compared by identity rather than by * `sessionKey`: provider provenance is what this value carries, and a * structural copy carrying the same key was issued by nobody. */ @@ -433,7 +433,7 @@ interface ManagedSession extends DetachedSession { /** * The runtime that created this handle. * - * Retained rather than looked up again: reaching for "the" runtime afterwards + * Saved rather than looked up again: reaching for "the" runtime afterwards * opens a second child for a session the first already owns, so every turn, * close, detach and teardown goes through the one that made the handle. */ @@ -506,7 +506,7 @@ function toError(value: unknown): Error { return value instanceof Error ? value : new Error(String(value)); } -/** The provider identity retained in a launch record. */ +/** The provider identity saved in a launch record. */ const ACPX_PROVIDER = "acpx"; /** Where this provider puts prepared instructions on a session it creates. */ @@ -554,8 +554,8 @@ class HostAssemblyIncomplete extends AgentSessionRecoveryRequired { * class means "already decided": anything else raised on the same path came * from private setup and says nothing repeatable. */ -class RetainedRefusal extends Error { - override name = "RetainedRefusal"; +class SavedRefusal extends Error { + override name = "SavedRefusal"; constructor(readonly failure: LaunchFailure) { super(failure.message); } @@ -566,7 +566,7 @@ class RetainedRefusal extends Error { * * `` and `` raise rather than retain, so the same settled * decision travels as an ordinary error with its stable class attached. A - * launch that meets one turns it back into a retained refusal. + * launch that meets one turns it back into a saved refusal. */ class AttachmentRefused extends Error { override name = "AttachmentRefused"; @@ -600,11 +600,11 @@ interface LaunchInvocation { */ readonly configured: Set; /** - * Sessions whose retained record this invocation has already checked against + * Sessions whose saved record this invocation has already checked against * its route. * * A replay reconciles before its own first live phase, and which phase that - * is depends on what the journal retained — detach for one suffix, exit for + * is depends on what the journal saved — detach for one suffix, exit for * the other. Recording it is what keeps the check from running twice. */ readonly reconciled: Set; @@ -1892,7 +1892,7 @@ function* useAcpxProviderState( const version = binding.version(observed.versionOutput); if (version === undefined) { // Deliberately without the raw output: it is provider-private, and this - // message is retained in a diagnostic. + // message is saved in a diagnostic. throw new AttachmentRefused({ class: "executable-binding-refused", message: @@ -1917,13 +1917,13 @@ function* useAcpxProviderState( /** The stable comparison two builds of one session fail. */ function buildDrift( sessionKey: string, - retained: ExecutableBuildBindingV1, + saved: ExecutableBuildBindingV1, live: ExecutableBuildBindingV1, ): LaunchFailure { return { class: "executable-binding-refused", message: - `session "${sessionKey}" was created by ${retained.reportedVersion} and this run ` + + `session "${sessionKey}" was created by ${saved.reportedVersion} and this run ` + `would use ${live.reportedVersion}, so the conversation it names cannot be confirmed`, }; } @@ -1937,7 +1937,7 @@ function* useAcpxProviderState( * disagree — a record asserting another conversation, or asserting none at * all, describes provider state this session cannot account for. */ - function* retainedAssertion(sessionKey: string, nativeSessionId: string): Operation { + function* savedAssertion(sessionKey: string, nativeSessionId: string): Operation { const record = yield* until(store.load(sessionKey)); if (record === undefined) { return; @@ -2110,7 +2110,7 @@ function* useAcpxProviderState( buildDrift(prepared.sessionKey, route.executableBinding, build.binding), ); } - yield* retainedAssertion(prepared.sessionKey, route.nativeSessionId); + yield* savedAssertion(prepared.sessionKey, route.nativeSessionId); return { build, resumeSessionId: route.nativeSessionId }; } @@ -2839,7 +2839,7 @@ function* useAcpxProviderState( // with nothing written into the record it never had. if (route?.route === "client-native" && route.schema === "session-route.v1") { invocation.fresh.set(sessionKey, false); - return retained(agentName, adapter, route, instructions, sessionCwd, "resumed"); + return saved(agentName, adapter, route, instructions, sessionCwd, "resumed"); } // Observed before an identity exists, so a build this run cannot name stops @@ -2867,7 +2867,7 @@ function* useAcpxProviderState( if (route?.route === "client-native") { invocation.fresh.set(sessionKey, false); invocation.bound.set(sessionKey, { build, adapter }); - return retained(agentName, adapter, route, instructions, sessionCwd, "resumed"); + return saved(agentName, adapter, route, instructions, sessionCwd, "resumed"); } // The adapter allocates, because only it knows what identity this provider @@ -2876,7 +2876,7 @@ function* useAcpxProviderState( const candidate = adapter.allocate(); // Publish or adopt: a caller meeting a compatible existing route takes its - // retained identity rather than insisting its own candidate win, because + // saved identity rather than insisting its own candidate win, because // the first durable publication is authoritative. let winner: AgentSessionRoute; try { @@ -2916,7 +2916,7 @@ function* useAcpxProviderState( // observation as that session's history. if (winner.schema === "session-route.v1") { invocation.fresh.set(sessionKey, false); - return retained(agentName, adapter, winner, instructions, sessionCwd, "resumed"); + return saved(agentName, adapter, winner, instructions, sessionCwd, "resumed"); } if (!sameExecutableBuild(winner.executableBinding, build.binding)) { const drift = buildDrift(sessionKey, winner.executableBinding, build.binding); @@ -2930,7 +2930,7 @@ function* useAcpxProviderState( invocation.fresh.set(sessionKey, fresh); invocation.bound.set(sessionKey, { build, adapter }); void prepared; - return retained( + return saved( agentName, adapter, winner, @@ -2941,7 +2941,7 @@ function* useAcpxProviderState( } /** The prepared record a client-native route describes, exactly. */ - function retained( + function saved( agentName: string, adapter: ClientAllocatedAdapter, route: Extract, @@ -2976,8 +2976,8 @@ function* useAcpxProviderState( return record; } - /** What a retained preparation says its conversation was put under. */ - function retainedConfiguration(prepared: PreparedLaunchRecord): SessionConfiguration | undefined { + /** What a saved preparation says its conversation was put under. */ + function savedConfiguration(prepared: PreparedLaunchRecord): SessionConfiguration | undefined { return prepared.configuration; } @@ -3114,7 +3114,7 @@ function* useAcpxProviderState( // owed. A resumed session has already been spoken in — whatever made it // openable happened before this run — so taking a turn in it would be // spending someone's model turn to learn nothing. The request id is minted - // here rather than at the turn so a replay reading the retained record is + // here rather than at the turn so a replay reading the saved record is // looking for the turn the first attempt committed to. const plan: MaterializationPlan | undefined = sessionState === "created" && adapter.materialization @@ -3238,15 +3238,15 @@ function* useAcpxProviderState( * Reached at a replay's own first live phase, which is wherever the journal * stopped: the detach for a launch that never handed over, and the spawn for * one that did. The session is reattached by the exact identity the - * preparation retained, configured, and given up again — a replay reopens a + * preparation saved, configured, and given up again — a replay reopens a * conversation, it does not author a second account of relinquishing it. */ - function* reconfigureRetained( + function* reconfigureSaved( invocation: LaunchInvocation, prepared: PreparedLaunchRecord, agentCommand: string, ): Operation { - const requested = retainedConfiguration(prepared); + const requested = savedConfiguration(prepared); if (requested === undefined || invocation.configured.has(prepared.sessionKey)) { return undefined; } @@ -3359,7 +3359,7 @@ function* useAcpxProviderState( * Reached only for a preparation that planned one, which is only ever a * conversation this launch created through an adapter that says a fresh one is * not yet openable. Everything about it is fixed before it runs: the exact - * prompt and the request id come from the retained plan, so a replay is + * prompt and the request id come from the saved plan, so a replay is * looking for this turn rather than authorizing another. * * It is not a Prompt. It publishes no event stream, reaches no authored @@ -3657,7 +3657,7 @@ function* useAcpxProviderState( // A replay that stopped before handing over reaches its conversation here // for the first time, so this is where what it was prepared with is put // back into force — before the detach that follows says the handoff began. - const unconfigured = yield* reconfigureRetained(invocation, prepared, agentCommand); + const unconfigured = yield* reconfigureSaved(invocation, prepared, agentCommand); if (unconfigured) { return { phase: "detached", failure: unconfigured }; } @@ -3778,7 +3778,7 @@ function* useAcpxProviderState( ); } catch (error) { // Contention and an unrecovered session are launch outcomes, not - // crashes: they are retained as a refusal, before any ACP ensure, + // crashes: they are saved as a refusal, before any ACP ensure, // detach or child, so the reader is told what to do about it. const refusal = ownershipRefusal(error, agentName, placement.sessionKey, callerCwd); if (!refusal) { @@ -3793,11 +3793,11 @@ function* useAcpxProviderState( /** * What this run must ask the native process to do. * - * The retained phase is the whole question, and it is answered by what the + * The saved phase is the whole question, and it is answered by what the * journal gave back rather than by anything the provider kept. A replay that - * retained only `prepared` proves the handoff never began — core retains + * saved only `prepared` proves the handoff never began — core retains * `detached` before it invokes this phase — so creation may still be owed. A - * replay that retained `detached` may have had a process start under this + * replay that saved `detached` may have had a process start under this * identity already, so it resumes and never falls back to creating. */ function* nativeCommand( @@ -3814,7 +3814,7 @@ function* useAcpxProviderState( // prepared-only one has already been checked at its own. const refused = yield* reconcile(invocation, prepared, agentCommand); if (refused) { - return Err(new RetainedRefusal(refused)); + return Err(new SavedRefusal(refused)); } } // Creation is a question only for an identity XMD chose. A session the @@ -3840,7 +3840,7 @@ function* useAcpxProviderState( return Ok(observed ? [observed.build.livePath, ...argv.slice(1)] : argv); } - /** Check a retained launch against its route once per invocation. */ + /** Check a saved launch against its route once per invocation. */ function* reconcile( invocation: LaunchInvocation, prepared: PreparedLaunchRecord, @@ -3849,7 +3849,7 @@ function* useAcpxProviderState( if (invocation.reconciled.has(prepared.sessionKey)) { return undefined; } - const refused = yield* reconcileRetainedLaunch(invocation, prepared, agentCommand); + const refused = yield* reconcileSavedLaunch(invocation, prepared, agentCommand); if (refused) { return refused; } @@ -3858,7 +3858,7 @@ function* useAcpxProviderState( } /** - * Check a retained launch against the route before resuming or creating. + * Check a saved launch against the route before resuming or creating. * * Two durable accounts of this session already exist — the journal's and the * route's — and a replay may act only if they still agree. Neither account @@ -3866,7 +3866,7 @@ function* useAcpxProviderState( * disagreement has discovered that the session it was going to continue is * not the session it prepared. */ - function* reconcileRetainedLaunch( + function* reconcileSavedLaunch( invocation: LaunchInvocation, prepared: PreparedLaunchRecord, agentCommand: string, @@ -3887,7 +3887,7 @@ function* useAcpxProviderState( `session "${prepared.sessionKey}" was prepared before XMD recorded which build its ` + `identity belongs to, so this run cannot confirm the conversation it names`, }); - // Which provider retained this preparation, before anything is compared + // Which provider saved this preparation, before anything is compared // against it. A record another provider wrote describes a session this one // does not own, and an ACPX route that happens to agree about a UUID is not // evidence that it does — it is two providers naming one string. @@ -3916,7 +3916,7 @@ function* useAcpxProviderState( } catch (error) { return stop(routeMessage(error)); } - let retainedBinding: ExecutableBuildBindingV1; + let savedBinding: ExecutableBuildBindingV1; if (allocatesIdentity(adapter)) { if (!route || route.route !== "client-native") { return stop( @@ -3935,7 +3935,7 @@ function* useAcpxProviderState( if (route.schema === "session-route.v1") { return unrecorded(); } - retainedBinding = route.executableBinding; + savedBinding = route.executableBinding; } else { if (!route || route.route !== "acp-first") { return stop( @@ -3946,13 +3946,13 @@ function* useAcpxProviderState( if (route.schema !== "session-route.v3") { return unrecorded(); } - retainedBinding = route.executableBinding; + savedBinding = route.executableBinding; // ACPX's own record, which is what any later ACP operation would reopen // this session through. A record naming another conversation, or none, // means the identity the native process is about to be handed is not the // one this provider would come back to. try { - yield* retainedAssertion(prepared.sessionKey, prepared.nativeSessionId); + yield* savedAssertion(prepared.sessionKey, prepared.nativeSessionId); } catch (error) { if (error instanceof AttachmentRefused) { return error.failure; @@ -3963,7 +3963,7 @@ function* useAcpxProviderState( if (prepared.executableBinding === undefined) { return unrecorded(); } - if (!sameExecutableBuild(retainedBinding, prepared.executableBinding)) { + if (!sameExecutableBuild(savedBinding, prepared.executableBinding)) { return disagree(); } // The live half, before the first effect a native process could act on. @@ -3976,8 +3976,8 @@ function* useAcpxProviderState( } throw error; } - if (!sameExecutableBuild(build.binding, retainedBinding)) { - return buildDrift(prepared.sessionKey, retainedBinding, build.binding); + if (!sameExecutableBuild(build.binding, savedBinding)) { + return buildDrift(prepared.sessionKey, savedBinding, build.binding); } invocation.bound.set(prepared.sessionKey, { build, adapter }); return undefined; @@ -4021,11 +4021,11 @@ function* useAcpxProviderState( return { phase: "exited", failure: unconfirmed }; } } - // A launch whose detach was already retained reaches its conversation for + // A launch whose detach was already saved reaches its conversation for // the first time here. It is configured and relinquished again before the // UI opens, and nothing durable is appended for that: the detach this // replay is standing on is the one that happened. - const unconfigured = yield* reconfigureRetained(invocation, prepared, agentCommand); + const unconfigured = yield* reconfigureSaved(invocation, prepared, agentCommand); if (unconfigured) { return { phase: "exited", failure: unconfigured }; } @@ -4042,7 +4042,7 @@ function* useAcpxProviderState( // Only a refusal this provider already settled says anything; a failure // of any other kind came from that same private path. const failure = - resolved.error instanceof RetainedRefusal ? resolved.error.failure : privateFailure(); + resolved.error instanceof SavedRefusal ? resolved.error.failure : privateFailure(); return { phase: "exited", failure }; } const command = resolved.value; @@ -4064,7 +4064,7 @@ function* useAcpxProviderState( } } - /** The retained refusal an ownership failure becomes, if it is one. */ + /** The saved refusal an ownership failure becomes, if it is one. */ function ownershipRefusal( error: unknown, agentName: string, diff --git a/packages/acp/src/session-route.ts b/packages/acp/src/session-route.ts index 0f62bc718..6abd1a633 100644 --- a/packages/acp/src/session-route.ts +++ b/packages/acp/src/session-route.ts @@ -139,7 +139,7 @@ function exactly(value: Record, members: readonly string[]): bo * boundary, so it is unknown state and fails closed like any other. */ /** - * Read a retained build binding strictly. + * Read a saved build binding strictly. * * The member set is exact rather than minimal, because a binding is compared * for equality: a member this build ignores is a fact the writer thought was diff --git a/packages/acp/src/session-store.ts b/packages/acp/src/session-store.ts index 74ad89d36..32b85df68 100644 --- a/packages/acp/src/session-store.ts +++ b/packages/acp/src/session-store.ts @@ -24,23 +24,23 @@ export function createAcpxSessionStore(stateDir: string): AcpxSessionStore { } /** What a store still holds for one session key. */ -export interface AcpxRetainedSession { +export interface AcpxSavedSession { readonly agentCommand: string; /** The provider-native session identity, when the adapter asserted one. */ readonly agentSessionId?: string; } /** The session this store retains under `sessionKey`, creating nothing. */ -export function* retainedSession( +export function* savedSession( store: AcpxSessionStore, sessionKey: string, -): Operation { +): Operation { const record = yield* until(store.load(sessionKey)); if (!record) { return undefined; } - const retained: AcpxRetainedSession = { agentCommand: record.agentCommand }; + const saved: AcpxSavedSession = { agentCommand: record.agentCommand }; return record.agentSessionId === undefined - ? retained - : { ...retained, agentSessionId: record.agentSessionId }; + ? saved + : { ...saved, agentSessionId: record.agentSessionId }; } diff --git a/packages/acp/tests/fixtures/claude-native-launch-proof.ts b/packages/acp/tests/fixtures/claude-native-launch-proof.ts index c0182b8f7..3b2d2e1ab 100644 --- a/packages/acp/tests/fixtures/claude-native-launch-proof.ts +++ b/packages/acp/tests/fixtures/claude-native-launch-proof.ts @@ -1028,7 +1028,7 @@ interface LaunchRecord { /** * The launch records one diagnostic journal holds, in order. * - * A retained preparation carries the prepared instruction text, so this reads + * A saved preparation carries the prepared instruction text, so this reads * it and nothing that reaches a verdict repeats it: what crosses out of here is * whether a digest exists, never the text or the digest's own bytes. */ @@ -1612,8 +1612,8 @@ function decideZeroTurn(verdict: JourneyVerdict): void { verdict.verdict = "PASS"; verdict.detail = verdict.outcome === "same-identity" - ? "re-entry reached its conversation surface under the exact retained identity" - : "re-entry refused the exact retained identity, and XMD failed closed"; + ? "re-entry reached its conversation surface under the exact saved identity" + : "re-entry refused the exact saved identity, and XMD failed closed"; } function decideTwoTurn(verdict: JourneyVerdict): void { diff --git a/packages/acp/tests/fixtures/claude-native-to-acp-proof.ts b/packages/acp/tests/fixtures/claude-native-to-acp-proof.ts index 007aac289..8f4ea0283 100644 --- a/packages/acp/tests/fixtures/claude-native-to-acp-proof.ts +++ b/packages/acp/tests/fixtures/claude-native-to-acp-proof.ts @@ -1107,7 +1107,7 @@ interface LaunchRecord { /** * The launch records one diagnostic journal holds, in order. * - * A retained preparation carries the prepared instruction text, so this reads + * A saved preparation carries the prepared instruction text, so this reads * it and nothing that reaches a verdict repeats it: what crosses out of here is * whether a digest exists, never the text or the digest's own bytes. */ @@ -1585,7 +1585,7 @@ function* settleAccounts(journey: Journey, verdict: JourneyVerdict): Operation): LaunchRecord | unde /** * The launch records one diagnostic journal holds, in order. * - * A retained preparation carries the prepared instruction text, so this reads it + * A saved preparation carries the prepared instruction text, so this reads it * and nothing that reaches a verdict repeats it: what crosses out of here is * whether a digest exists, never the text or the digest's own bytes. */ @@ -1576,7 +1576,7 @@ function* settleAccounts(journey: Journey, verdict: JourneyVerdict): Operation record.phase === "materialized"); verdict.materializationTurns = materialized.length; @@ -1716,7 +1716,7 @@ function* ready(journey: Journey, verdict: JourneyVerdict): Operation { * Materialize once, leave without saying anything, and come back. * * The second invocation is the question: a session that is already resumable - * owes no turn, so a materialization retained twice would mean a reader paid + * owes no turn, so a materialization saved twice would mean a reader paid * again for something that had already been done. */ function* runZeroNativeTurn(journey: Journey, verdict: JourneyVerdict): Operation { @@ -1860,7 +1860,7 @@ function* runNativeLaunch( function decideZeroNativeTurn(verdict: JourneyVerdict): void { if (verdict.materializationTurns !== 1) { verdict.verdict = "PRODUCT_FAILED"; - verdict.detail = `the journey retained ${verdict.materializationTurns} materialization turns`; + verdict.detail = `the journey saved ${verdict.materializationTurns} materialization turns`; return; } if (verdict.nativeUserTurns !== 0) { @@ -1887,7 +1887,7 @@ function decideZeroNativeTurn(verdict: JourneyVerdict): void { function decideNativeLaunch(verdict: JourneyVerdict): void { if (verdict.materializationTurns !== 1) { verdict.verdict = "PRODUCT_FAILED"; - verdict.detail = `the journey retained ${verdict.materializationTurns} materialization turns`; + verdict.detail = `the journey saved ${verdict.materializationTurns} materialization turns`; return; } if (!verdict.openingSentenceExact) { diff --git a/packages/acp/tests/helpers.ts b/packages/acp/tests/helpers.ts index f0546cf0b..95752db3e 100644 --- a/packages/acp/tests/helpers.ts +++ b/packages/acp/tests/helpers.ts @@ -723,7 +723,7 @@ export interface CoordinatorHarness { export function makeCoordinator(): CoordinatorHarness { const occupied = new Set(); - const retained = new Map(); + const saved = new Map(); const acquisitions: CoordinatorHarness["acquisitions"] = []; const order: string[] = []; @@ -731,7 +731,7 @@ export function makeCoordinator(): CoordinatorHarness { acquisitions, events: order, tombstone(key) { - retained.set(agentSessionKeyDigest(key), "active"); + saved.set(agentSessionKeyDigest(key), "active"); }, coordinator: { coordinate( @@ -749,14 +749,14 @@ export function makeCoordinator(): CoordinatorHarness { yield* ensure(() => { occupied.delete(digest); }); - if (retained.get(digest) === "active") { + if (saved.get(digest) === "active") { acquisitions.push({ kind: owner.kind, key, outcome: "recovery-required" }); return Err( new AgentSessionRecoveryRequired(`session "${key.sessionKey}" needs recovery`), ); } acquisitions.push({ kind: owner.kind, key, outcome: "granted" }); - retained.set(digest, "active"); + saved.set(digest, "active"); order.push("owned"); let quiesced = false; yield* ensure(() => { @@ -764,7 +764,7 @@ export function makeCoordinator(): CoordinatorHarness { // finalizer the body registers unwinds ahead of it. order.push(quiesced ? "released-idle" : "released-active"); if (quiesced) { - retained.set(digest, "idle"); + saved.set(digest, "idle"); } }); return Ok( diff --git a/packages/acp/tests/native-launch.test.ts b/packages/acp/tests/native-launch.test.ts index a0010fe6e..f6779d22a 100644 --- a/packages/acp/tests/native-launch.test.ts +++ b/packages/acp/tests/native-launch.test.ts @@ -259,9 +259,9 @@ interface ProviderOptions { * What the journal gives an incomplete replay back. * * The suffix is the whole question. A launch interrupted after preparing but - * before detaching retained only `prepared`, so its replay runs detach live and + * before detaching saved only `prepared`, so its replay runs detach live and * native creation may never have begun. One interrupted after detaching - * retained both, so the native process was already free to start and the replay + * saved both, so the native process was already free to start and the replay * may do nothing but resume. */ interface Replay { @@ -318,7 +318,7 @@ function traceCoordinator(trace: Trace): AgentLaunchCoordinator { throw new Error("this stub coordinator names no provider turn"); }, *perform(_request, phases) { - // A replay hands back what the journal retained rather than calling the + // A replay hands back what the journal saved rather than calling the // provider's live preparation, exactly as the real coordinator does when // the phase is already recorded. const replay = trace.replay; @@ -453,13 +453,13 @@ function newTrace(): Trace { return trace; } -/** The preparation a launch retained, refusing a trace that holds none. */ +/** The preparation a launch saved, refusing a trace that holds none. */ function preparedOf(trace: Trace): PreparedLaunchRecord { const record = trace.records.find( (candidate): candidate is PreparedLaunchRecord => candidate.phase === "prepared", ); if (!record) { - throw new Error("this launch retained no preparation"); + throw new Error("this launch saved no preparation"); } return record; } @@ -532,7 +532,7 @@ function* configuredUse(trace: Trace, configuration: SessionConfiguration): Oper } } -/** Run a launch and report the refusal it retained, if it retained one. */ +/** Run a launch and report the refusal it saved, if it saved one. */ function* attempt( trace: Trace, instructions: string, @@ -698,7 +698,7 @@ describe("Tier NL — native session launch", () => { yield* launch(INSTRUCTIONS); const nativeSessionId = `agent-session:${SESSION_KEY}`; - // Read off the retained preparation, because that is where the identity + // Read off the saved preparation, because that is where the identity // is authored: the public launch route answers nothing at all. const prepared = trace.records[0] as PreparedLaunchRecord; expect(prepared.nativeSessionId).toBe(nativeSessionId); @@ -727,7 +727,7 @@ describe("Tier NL — native session launch", () => { }); }); - it("NL8: the retained record carries cwd, permissions and the instruction channel", function* () { + it("NL8: the saved record carries cwd, permissions and the instruction channel", function* () { const harness = createFakeRuntime(); const trace = newTrace(); yield* scoped(function* () { @@ -1225,7 +1225,7 @@ describe("Tier NO — session ownership", () => { return yield* attempt(contenderTrace, INSTRUCTIONS); }); - // Answered immediately, and retained as a refusal rather than raised: + // Answered immediately, and saved as a refusal rather than raised: // the reader is told what to do about it. expect(refusal?.class).toBe("session-busy"); // The contender did no ACP work and started no child. @@ -1477,7 +1477,7 @@ describe("Tier NO — session ownership", () => { ); expect(harness.turns).toHaveLength(2); expect(harness.turns.map((turn) => turn.input.text)).toEqual(["first", "second"]); - // One retained identity, in the provider's own store: one record, no + // One saved identity, in the provider's own store: one record, no // longer awaiting materialization, asserting the conversation both turns // were spent in. expect([...store.records.keys()]).toEqual([SESSION_KEY]); @@ -1991,7 +1991,7 @@ describe("Tier CN — client-allocated construction", () => { // The strict store raises; the launch surface turns that into the failure // the reader asked about. A session cannot be confirmed, so the launch // says so — rather than the error travelling past the launch and leaving - // it with no retained phase at all. + // it with no saved phase at all. const harness = createFakeRuntime(); const trace = newTrace(); const routes: AgentSessionRouteStore = { @@ -2064,7 +2064,7 @@ describe("Tier CN — client-allocated construction", () => { * Tier CR — resuming a client-allocated launch that did not finish * (specs/native-agent-session-launch-spec.md §Replay). * - * The retained phase is the whole answer. Core retains `detached` before it + * The saved phase is the whole answer. Core retains `detached` before it * invokes the exit phase, so a journal holding only `prepared` proves the * handoff never began and creation may still be owed. A journal that reached * `detached` may already have had a process start under this identity, so its @@ -2088,8 +2088,8 @@ describe("Tier CR — client-allocated incomplete replay", () => { resume: (nativeSessionId) => ["claude", "--resume", nativeSessionId], }; - /** A retained prepared record, as an interrupted launch would have left one. */ - function retained(overrides: Partial = {}): PreparedLaunchRecord { + /** A saved prepared record, as an interrupted launch would have left one. */ + function saved(overrides: Partial = {}): PreparedLaunchRecord { return { phase: "prepared", agent: "claude", @@ -2151,7 +2151,7 @@ describe("Tier CR — client-allocated incomplete replay", () => { adapters: { claude: ADAPTER }, routeStore: routes, }); - trace.replay = { prepared: retained(overrides), suffix }; + trace.replay = { prepared: saved(overrides), suffix }; } it("CR1: a detached replay resumes, and never falls back to creating", function* () { @@ -2159,7 +2159,7 @@ describe("Tier CR — client-allocated incomplete replay", () => { const trace = newTrace(); const routes = createMemorySessionRouteStore(); yield* installReplay(harness, trace, routes, "prepared+detached"); - yield* routes.publish(agreeing(retained())); + yield* routes.publish(agreeing(saved())); yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); @@ -2167,16 +2167,16 @@ describe("Tier CR — client-allocated incomplete replay", () => { expect(harness.ensureCalls).toEqual([]); }); - it("CR2: a prepared-only replay creates under the retained identity", function* () { + it("CR2: a prepared-only replay creates under the saved identity", function* () { // The predecessor never detached, so nothing handed this session to a // native process and creation may still be owed. The identity is not // reallocated — a second one would be a second conversation — so the create - // argv carries the retained one, with a file this run wrote. + // argv carries the saved one, with a file this run wrote. const harness = createFakeRuntime(); const trace = newTrace(); const routes = createMemorySessionRouteStore(); yield* installReplay(harness, trace, routes, "prepared"); - yield* routes.publish(agreeing(retained())); + yield* routes.publish(agreeing(saved())); yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); @@ -2189,7 +2189,7 @@ describe("Tier CR — client-allocated incomplete replay", () => { it("CR3: one replay's live detach does not reach the next replay", function* () { // The two replays describe the same logical session and differ only in what - // their journals retained. If the first one's live detach outlived the + // their journals saved. If the first one's live detach outlived the // launch that observed it, the second — whose journal proves the session // was already handed over — would create a second conversation under an // identity that already names one. @@ -2197,10 +2197,10 @@ describe("Tier CR — client-allocated incomplete replay", () => { const trace = newTrace(); const routes = createMemorySessionRouteStore(); yield* installReplay(harness, trace, routes, "prepared"); - yield* routes.publish(agreeing(retained())); + yield* routes.publish(agreeing(saved())); yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); - trace.replay = { prepared: retained(), suffix: "prepared+detached" }; + trace.replay = { prepared: saved(), suffix: "prepared+detached" }; yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); expect(trace.launches.length).toBe(2); @@ -2264,12 +2264,9 @@ describe("Tier CR — client-allocated incomplete replay", () => { // about to retain — writing down that the handoff began is itself advancing // the launch. For a detached replay it is the spawn. const other = "99999999-0000-0000-0000-000000000000"; - const named = agreeing(retained()) as Extract< - AgentSessionRoute, - { schema: "session-route.v2" } - >; + const named = agreeing(saved()) as Extract; // Each case is one way the journal and the route can describe different - // sessions: a route that says something else, or a retained record another + // sessions: a route that says something else, or a saved record another // provider wrote. const disagreements: [string, AgentSessionRoute | undefined, Partial][] = [ @@ -2281,7 +2278,7 @@ describe("Tier CR — client-allocated incomplete replay", () => { // An agreeing ACPX route is not evidence that this provider owns the // session a different one prepared: that is two providers naming one // string. - ["a record another provider prepared", agreeing(retained()), { provider: "other" }], + ["a record another provider prepared", agreeing(saved()), { provider: "other" }], ]; for (const suffix of ["prepared", "prepared+detached"] as const) { @@ -3008,7 +3005,7 @@ describe("Tier CA — client-native attachment", () => { }); it("CA6: a moved build is the same build", function* () { - // Equality is over what was retained — a version and a digest — so the same + // Equality is over what was saved — a version and a digest — so the same // bytes reached at another path attach exactly as they would have before. const observer = createFakeObserver({ path: "/elsewhere/bin/claude" }); const space = yield* installAttachment(bound(), { observer: observer.observer }); @@ -3021,7 +3018,7 @@ describe("Tier CA — client-native attachment", () => { }); }); - it("CA7: retained provider arrangement that names something else refuses before ensure", function* () { + it("CA7: saved provider arrangement that names something else refuses before ensure", function* () { for (const [name, asserted] of [ ["another conversation", "99999999-0000-0000-0000-000000000000"], ["no conversation at all", undefined], @@ -3975,7 +3972,7 @@ describe("Tier LU — legacy unbound client-native", () => { yield* launch(INSTRUCTIONS); - // Exactly what the released contract does: the launcher name, the retained + // Exactly what the released contract does: the launcher name, the saved // identity, no build observed, and no binding invented for a session that // never recorded one. expect(trace.launches[0]!.command).toEqual(["claude", "--resume", ALLOCATED]); @@ -4278,7 +4275,7 @@ describe("Tier BA — bound ACP-first construction", () => { expect(refusal?.class).toBe("executable-binding-refused"); }); - // Nothing was ensured, nothing was spawned, and the retained account is + // Nothing was ensured, nothing was spawned, and the saved account is // exactly what it was: a drifted run repairs no route. expect(drifted.ensureCalls).toEqual([]); expect(second.launches).toEqual([]); @@ -4498,7 +4495,7 @@ describe("Tier MZ — the materialization turn", () => { expect(trace.notices[0]).toContain("codex-materialization.v1"); }); - it("MZ4: the assistant response is displayed whole, and retained whole", function* () { + it("MZ4: the assistant response is displayed whole, and saved whole", function* () { const harness = createFakeRuntime(); const trace = newTrace(); harness.script({ @@ -5032,7 +5029,7 @@ describe("Tier NC — configured native launch", () => { // One sequence, provider and launcher together: the session exists, the // model is applied, its effort choices are refreshed, the effort is // applied and verified, and only after all of that is the preparation - // retained, ACP let go of, and the native UI started. + // saved, ACP let go of, and the native UI started. expect(trace.order).toEqual([ "ensure", "status", @@ -5235,7 +5232,7 @@ describe("Tier NC — configured native launch", () => { * the one that happened. */ describe("Tier NR — configured incomplete replay", () => { - function retainedConfigured(overrides: Partial = {}): PreparedLaunchRecord { + function savedConfigured(overrides: Partial = {}): PreparedLaunchRecord { return { phase: "prepared", agent: "claude", @@ -5263,7 +5260,7 @@ describe("Tier NR — configured incomplete replay", () => { advertiseConfiguration(harness, trace); yield* scoped(function* () { yield* installLaunchStack(harness, trace); - trace.replay = { prepared: retainedConfigured(), suffix: "prepared" }; + trace.replay = { prepared: savedConfigured(), suffix: "prepared" }; yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); @@ -5321,7 +5318,7 @@ describe("Tier NR — configured incomplete replay", () => { }); yield* routes.publish(BOUND_ACP_ROUTE); trace.replay = { - prepared: retainedConfigured({ executableBinding: OBSERVED_BUILD }), + prepared: savedConfigured({ executableBinding: OBSERVED_BUILD }), suffix: "prepared+detached", }; @@ -5383,7 +5380,7 @@ describe("Tier NR — configured incomplete replay", () => { }); yield* routes.publish(BOUND_ACP_ROUTE); trace.replay = { - prepared: retainedConfigured({ executableBinding: OBSERVED_BUILD }), + prepared: savedConfigured({ executableBinding: OBSERVED_BUILD }), suffix: "prepared+detached", }; @@ -5435,7 +5432,7 @@ describe("Tier NR — configured incomplete replay", () => { }); yield* routes.publish(BOUND_ACP_ROUTE); trace.replay = { - prepared: retainedConfigured({ executableBinding: OBSERVED_BUILD }), + prepared: savedConfigured({ executableBinding: OBSERVED_BUILD }), suffix: "prepared+detached", }; @@ -5493,7 +5490,7 @@ describe("Tier NR — configured incomplete replay", () => { // Written as a released record would have it: the member is not part of // this build's shape, so it is added beside the record rather than // through its type. - const released = { ...retainedConfigured({ configuration: undefined }), model: "gpt-5.4" }; + const released = { ...savedConfigured({ configuration: undefined }), model: "gpt-5.4" }; trace.replay = { prepared: released, suffix: "prepared+detached" }; yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); @@ -5511,7 +5508,7 @@ describe("Tier NR — configured incomplete replay", () => { yield* scoped(function* () { yield* installLaunchStack(harness, trace); trace.replay = { - prepared: retainedConfigured({ configuration: undefined }), + prepared: savedConfigured({ configuration: undefined }), suffix: "prepared+detached", }; @@ -5534,7 +5531,7 @@ describe("Tier NR — configured incomplete replay", () => { ]; yield* scoped(function* () { yield* installLaunchStack(harness, trace); - trace.replay = { prepared: retainedConfigured(), suffix: "prepared+detached" }; + trace.replay = { prepared: savedConfigured(), suffix: "prepared+detached" }; yield* Agent.operations.launch(launchRequest(INSTRUCTIONS)); diff --git a/packages/acp/tests/provider.test.ts b/packages/acp/tests/provider.test.ts index b57129543..7700c5aa7 100644 --- a/packages/acp/tests/provider.test.ts +++ b/packages/acp/tests/provider.test.ts @@ -980,7 +980,7 @@ describe("Tier SM — session placement and materialization", () => { const log = yield* installStrictProvider(harness); const session = yield* Agent.operations.session(); - // Placed, and nothing retained: a key is occupied and no conversation is + // Placed, and nothing saved: a key is occupied and no conversation is // named, so the host has nothing to map yet. expect(log.places).toHaveLength(1); expect(log.established).toEqual([]); @@ -1083,7 +1083,7 @@ describe("Tier SM — session placement and materialization", () => { yield* first; yield* second; - // One backend creation, one retained identity, and two turns in the + // One backend creation, one saved identity, and two turns in the // conversation it established. expect(harness.ensureCalls.filter((call) => call.materialization !== undefined)).toHaveLength( 1, @@ -1385,7 +1385,7 @@ const ACP_SESSION_ID = "acp-session-workflow"; interface StrictHarness { /** Every placement the profile was asked for, in order. */ places: AcpxSessionContext[]; - /** Every identity the provider retained, in order. */ + /** Every identity the provider saved, in order. */ established: AcpxSessionIdentity[]; /** Public permission decisions the authored policy was asked for. */ consulted: number; @@ -1488,7 +1488,7 @@ describe("Tier WAP — strict workflow Agent profile", () => { const harness = createFakeRuntime(); yield* scoped(function* () { const log = yield* installStrictProvider(harness); - yield* collectPrompt("summarize the retained work"); + yield* collectPrompt("summarize the saved work"); const created = harness.createdOptions.find((options) => options.onPermissionRequest); expect(created?.cwd).toBe(HOST_DIR); @@ -1648,7 +1648,7 @@ describe("Tier WAP — strict workflow Agent profile", () => { }); }); - it("WAP5: a retained session this host cannot continue refuses before ACPX is contacted", function* () { + it("WAP5: a saved session this host cannot continue refuses before ACPX is contacted", function* () { const harness = createFakeRuntime(); const refusal = new Error("this run's Agent session was established under a different policy"); yield* scoped(function* () { diff --git a/packages/acp/tests/session-route.test.ts b/packages/acp/tests/session-route.test.ts index e6646a9dc..0c496e5e3 100644 --- a/packages/acp/tests/session-route.test.ts +++ b/packages/acp/tests/session-route.test.ts @@ -173,7 +173,7 @@ describe("Tier SR — the construction route", () => { // V1 is the released unbound form. A binding is a member that format // does not have, and an extra member is state this build cannot account // for — which is what keeps a V1 record from being read as a V2 one. - ["a retained build binding", { ...good, executableBinding: BUILD }], + ["a saved build binding", { ...good, executableBinding: BUILD }], ["an executable path", { ...good, executablePath: "/usr/local/bin/claude" }], // The suspended branch's shape was never a released compatibility // boundary, so it is unknown state like any other. diff --git a/packages/acp/tests/terminal-screen.test.ts b/packages/acp/tests/terminal-screen.test.ts index d2b2b8194..098b842e3 100644 --- a/packages/acp/tests/terminal-screen.test.ts +++ b/packages/acp/tests/terminal-screen.test.ts @@ -86,7 +86,7 @@ describe("Tier NA — native answer observation", () => { terminal.write(`${ANSWER}\r\n`); // On the shell's own screen the words are genuinely there, which is why a - // proof reading retained bytes would have accepted them. + // proof reading saved bytes would have accepted them. expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); terminal.write(ALT_ON); diff --git a/packages/acp/vendor/acpx/MANIFEST.json b/packages/acp/vendor/acpx/MANIFEST.json index ff9dfedbb..7f32df82c 100644 --- a/packages/acp/vendor/acpx/MANIFEST.json +++ b/packages/acp/vendor/acpx/MANIFEST.json @@ -26,7 +26,7 @@ { "path": "PROVENANCE.md", "kind": "provenance", - "sha256": "07444ab687840e22e16bbba09c80ed916f8aa9a8e222d30fa415c42d865695df" + "sha256": "44cf53c7295357ec86f89861f21a43ca187530ed6e9cb9116bd5c10047b592de" }, { "path": "generated/client-DwNyFauq.d.ts", diff --git a/packages/acp/vendor/acpx/PROVENANCE.md b/packages/acp/vendor/acpx/PROVENANCE.md index cc1416297..934fee8f1 100644 --- a/packages/acp/vendor/acpx/PROVENANCE.md +++ b/packages/acp/vendor/acpx/PROVENANCE.md @@ -137,7 +137,7 @@ from leaving a session that looks established. Nothing infers acceptance. No stop reason, status, event, error code or diagnostic promotes a record, and the marker is never appended to a -conversation, emitted as an event, exposed as prompt text, or retained as a +conversation, emitted as an event, exposed as prompt text, or saved as a checkpoint token. ## Packaging adaptations diff --git a/packages/acp/vendor/adapters/PROVENANCE.md b/packages/acp/vendor/adapters/PROVENANCE.md index b9342c3cc..ec2a80072 100644 --- a/packages/acp/vendor/adapters/PROVENANCE.md +++ b/packages/acp/vendor/adapters/PROVENANCE.md @@ -160,7 +160,7 @@ carried here. A missing acceptance marker is a different matter, and deliberately so: no fallback promotes a session in its place, so a first turn that never reports -acceptance leaves the session unestablished rather than silently retained. +acceptance leaves the session unestablished rather than silently saved. A missing checkpoint affects only association retention. An agent that reports no turn identity — because this build overrides nothing for it, or because the diff --git a/packages/cli/src/agent-options.ts b/packages/cli/src/agent-options.ts index f40fc80ae..cf0d80bd5 100644 --- a/packages/cli/src/agent-options.ts +++ b/packages/cli/src/agent-options.ts @@ -10,7 +10,7 @@ * * The grammar is fixed rather than declared, because the parser this CLI * declares its commands with passes an option it does not recognize through to - * the command. Here that would mean starting an agent — and an unretained + * the command. Here that would mean starting an agent — and an unsaved * conversation in someone's history — for a command line that was already * wrong. */ diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 1379e5383..da0f49a76 100755 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -68,7 +68,7 @@ import { inspectDocument, agentIdentityComponents, installAgentComponents, - retainedSource, + savedSource, rootSourcePath, useNormalizedOutput, useTerminalOutput, @@ -300,10 +300,10 @@ const runConfig = object({ const REPL_DESCRIPTION = "Run and reconstruct one XMD entry in an interactive terminal."; /** - * `xmd repl` — one entry, one terminal, one retained history. + * `xmd repl` — one entry, one terminal, one saved history. * * The optional positional is a location this command printed earlier. With one, - * the command reopens exactly that retained history and selects exactly what the + * the command reopens exactly that saved history and selects exactly what the * location names; with none, it starts a fresh execution with an empty draft. * * The entry a person types may run Agent work, so this command configures the @@ -317,7 +317,7 @@ const replConfig = object({ location: { description: "a location this REPL printed earlier — `xmd://repl//...` — to reopen " + - "that retained history and the exact view it names", + "that saved history and the exact view it names", ...field(z.string().optional(), cli.argument()), }, ...agentFields, @@ -477,7 +477,7 @@ const AGENT_DESCRIPTION = "Report the model and effort choices an agent advertis * * It answers the one thing the option list cannot: asking costs a conversation * in the agent's own history. Nothing is said in it and nothing of this run is - * retained, but a provider that keeps its sessions keeps that one. + * saved, but a provider that keeps its sessions keeps that one. */ const AGENT_OPTIONS_HELP = [ "Inspecting an agent reads the choices it advertises for a session, so xmd", @@ -584,7 +584,7 @@ const REPL_HELP = [ " An execution admits exactly one entry. The screen always shows a location", " of the form xmd://repl//..., and when the command ends it", " prints the whole xmd repl that reopens the one it ended at. Running", - " it reopens the same retained history and selects the same view — in this", + " it reopens the same saved history and selects the same view — in this", " process or another one, from the history file alone.", ].join("\n"); @@ -1074,7 +1074,7 @@ interface DocumentConfig { /** * The journal this execution reads and appends, when the caller owns one. * - * A workflow run does: its journal is the run's retained history, and + * A workflow run does: its journal is the run's saved history, and * replacing it with a fresh stream would make every execution a first one. * `xmd run` supplies none and gets the empty stream below. */ @@ -1144,7 +1144,7 @@ export interface DocumentMode { /** * What this command's Plugins declared and installed. * - * The retained assembly, never a second installation. The Plugin values + * The saved assembly, never a second installation. The Plugin values * travel with it so an isolated nested run can install them again in its own * scope, which is the one place a second installation is correct. */ @@ -1823,7 +1823,7 @@ interface PropsPhase { * The immutable definition a `workflow start` established, when it did. * * Established here rather than later because the props a run is created with - * are the ones the *retained* document declares: binding against one reading + * are the ones the *saved* document declares: binding against one reading * of the file and then executing another would let help and parsing describe * a document that is not the one running. */ @@ -2087,7 +2087,7 @@ function* preparePropsPhase( if (!input.ok) { return { args: fixed, bindings: [], error: STANDARD_INPUT_FAILURE }; } - root = retainedSource(STANDARD_INPUT_PATH, input.value); + root = savedSource(STANDARD_INPUT_PATH, input.value); } else if (first !== undefined) { const reference = readReference(first); if (!reference.ok) { @@ -2164,9 +2164,9 @@ function exactRoot(root: RootDocumentSource, target: string | undefined): RootDo * `--props-*` arguments are exactly `xmd run`'s for that document. A `fork` * reads the same way, from the definition it names as its third argument: the * fork is a run of that document, so its props are that document's — merged - * over what the source retained, which happens later, once the run store can be + * over what the source saved, which happens later, once the run store can be * read. `resume` declares nothing at all: its props are the ones the run - * retained, and any spelling that would supply new ones is refused rather than + * saved, and any spelling that would supply new ones is refused rather than * ignored. */ function* prepareWorkflowProps( @@ -2219,7 +2219,7 @@ function* prepareWorkflowProps( workflow, error: `unrecognized option for xmd workflow ${action}: ${stray} — document properties ` + - "belong to the start that created the run, whose props it retained", + "belong to the start that created the run, whose props it saved", }; } return { args, bindings: [], workflow }; @@ -2235,7 +2235,7 @@ function* prepareWorkflowProps( } const { definition } = established.value; - const root = retainedSource(definition.entrypoint, established.value.source, { + const root = savedSource(definition.entrypoint, established.value.source, { ...(definition.targetPath === undefined ? {} : { target: definition.targetPath }), }); try { @@ -3211,7 +3211,7 @@ function* dispatch( established: propsPhase.established, props: props.value ?? {}, // What the *candidate* declares. A fork merges the properties its - // source retained under its own, and the result is held to the + // source saved under its own, and the result is held to the // document that is about to run. propsSchema: propsPhase.propsSchema ?? {}, }; @@ -3232,7 +3232,7 @@ function* dispatch( secretDetection: request.secretDetection, stream: execution.stream, // A workflow owns its journal, so its process results are part of - // the run's retained history: a resumed procedure reads back what + // the run's saved history: a resumed procedure reads back what // its commands printed rather than re-running them to find out. // It names no `--journal`, which is exactly why this is stated // here and never derived from that pathname. @@ -3410,7 +3410,7 @@ function* runCommand( const helpRequest = takeHelpFlag(evalFlags.rest); // Before the props phase, because that phase establishes a workflow start's - // definition in order to read what the *retained* document declares. On a + // definition in order to read what the *saved* document declares. On a // host without workflow support the first thing a caller would otherwise see // is a refusal about their document, which is not the reason the command is // not going to run. Help is exempt: the grammar is the same everywhere, and diff --git a/packages/cli/src/documents/Plan.md b/packages/cli/src/documents/Plan.md index 2a4dc3adb..577fc3ea2 100644 --- a/packages/cli/src/documents/Plan.md +++ b/packages/cli/src/documents/Plan.md @@ -694,7 +694,7 @@ The coding agent produced this draft: Only an approved Plan leaves this workflow, and only after the whole authorship frame above has been taken down. Its exact source is checked once more as -structure — every declaration, resolution and form it uses — and retained +structure — every declaration, resolution and form it uses — and saved together with the prompt it was written for, so asking again restores these exact bytes instead of writing a second Plan. diff --git a/packages/cli/src/plan-args.ts b/packages/cli/src/plan-args.ts index 3d1f03ffe..9dcf012cb 100644 --- a/packages/cli/src/plan-args.ts +++ b/packages/cli/src/plan-args.ts @@ -92,7 +92,7 @@ const SHORT_ALIASES: ReadonlyMap = new Map([ ["-j", `${JOURNAL_OPTION} `], ]); -/** What a short alias of a retained authorship option is answered with. */ +/** What a short alias of a saved authorship option is answered with. */ export function shortAliasRefusal(option: string): string { return `unrecognized option for xmd plan: ${option} — write \`${SHORT_ALIASES.get(option)}\``; } @@ -150,11 +150,11 @@ function optionName(token: string): string { * * Asked only where a value option would otherwise swallow the token after it. * A removed spelling is already refused where it stands, and a bare `-` is a - * file called `-` rather than an option, so what is left is the retained + * file called `-` rather than an option, so what is left is the saved * grammar: the tokens that mean something here and therefore cannot also be a * caller's filename. */ -function namesRetainedOption(token: string): boolean { +function namesSavedOption(token: string): boolean { if (!token.startsWith("-") || token === "-") { return false; } @@ -311,7 +311,7 @@ export function scanPlanArgs(args: readonly string[]): PlanScan { const separated = equals === -1 && VALUE.has(name) && next !== undefined ? next : undefined; if (separated !== undefined) { // A removed option is refused where it stands, including where a - // retained option would otherwise swallow it: `--include --run` names + // saved option would otherwise swallow it: `--include --run` names // a directory nobody has, and reading it as one is how a removed // spelling survives. const swallowed = tokenRemoval(separated); @@ -340,7 +340,7 @@ export function scanPlanArgs(args: readonly string[]): PlanScan { // — silently, because a path is a path. const value = equals === -1 - ? separated !== undefined && namesRetainedOption(separated) + ? separated !== undefined && namesSavedOption(separated) ? undefined : separated : token.slice(equals + 1); diff --git a/packages/cli/src/plan-component.ts b/packages/cli/src/plan-component.ts index b7e0fa4ea..78e3361c4 100644 --- a/packages/cli/src/plan-component.ts +++ b/packages/cli/src/plan-component.ts @@ -54,7 +54,7 @@ import { agentIdentityComponents, content, DocumentOutput, - retainedSource, + savedSource, scanSecrets, SecretDetectedError, secretPolicy, @@ -150,7 +150,7 @@ export function structuralValidation( scoped(function* (): Operation { yield* useCommandComponents(); return yield* validateDocumentStructure({ - ...retainedSource(PLAN_IDENTITY, candidate), + ...savedSource(PLAN_IDENTITY, candidate), includes: [...includes], components: agentIdentityComponents(), // Whatever the installed Plugins declare travels with the profile, so a @@ -242,7 +242,7 @@ export interface PlanComponentAssembly { /** * What the command's Plugins declared, when this surface has them. * - * The retained assembly, never a second installation: a `` inside a run + * The saved assembly, never a second installation: a `` inside a run * is checked against the vocabulary that run installed, and reinstalling here * would re-read every packaged asset to arrive at the same answer. */ @@ -266,7 +266,7 @@ const INPUTS_RETURNS = { * What the frozen inputs are given: the caller's optional session name and the * prompt this invocation is about. * - * Only the prompt's digest is retained here. The vocabulary the Agent is shown + * Only the prompt's digest is saved here. The vocabulary the Agent is shown * is public ``'s, with a durable record of its own, so each protocol * can be read and reconciled independently. */ @@ -555,11 +555,11 @@ function planInputs(assembly: PlanComponentAssembly): IdentityComponent { }); // A history is input, so it is parsed rather than trusted. - const retained = readInputs(frozen); - if (retained === undefined) { + const saved = readInputs(frozen); + if (saved === undefined) { throw new StaleInputError(UNREADABLE_INPUTS); } - if (retained.instruction !== instruction) { + if (saved.instruction !== instruction) { throw new StaleInputError(STALE); } @@ -738,7 +738,7 @@ function planProgress(assembly: PlanComponentAssembly): IdentityComponent { /** * Answer about one draft, without executing a byte of it. * - * Each assessment is retained under this expansion and the exact candidate it + * Each assessment is saved under this expansion and the exact candidate it * was about, so a continuation restores the answer the run gave rather than * re-deciding it against a working tree that has moved. The candidate itself * never reaches the description — only the digest that tells two drafts apart. @@ -906,8 +906,8 @@ function* withholdSecrets(text: string): Operation { * in — no trimming, no fence removal, no normalization, no added newline. */ -/** What the frozen inputs retained, or nothing when the record is not one. */ -interface RetainedInputs { +/** What the frozen inputs saved, or nothing when the record is not one. */ +interface SavedInputs { readonly instruction: string; } @@ -919,7 +919,7 @@ interface RetainedInputs { * version cannot read — not one to fill in a default for, because every default * here is a guess about what an earlier run actually asked. */ -function readInputs(value: Json): RetainedInputs | undefined { +function readInputs(value: Json): SavedInputs | undefined { if (typeof value !== "object" || value === null || Array.isArray(value)) { return undefined; } @@ -943,7 +943,7 @@ function readInputs(value: Json): RetainedInputs | undefined { * instead of what was actually wrong with their history. */ const UNREADABLE_INPUTS = - "the retained Plan inputs cannot be read as Plan inputs, so no Plan source was produced."; + "the saved Plan inputs cannot be read as Plan inputs, so no Plan source was produced."; /** * What a continuation asking for a different Plan is refused with. @@ -955,14 +955,14 @@ const UNREADABLE_INPUTS = * instead of a count of unreached journal entries. */ const STALE = - "this Plan was written for different instructions, so the retained Plan is stale input. " + + "this Plan was written for different instructions, so the saved Plan is stale input. " + "No Plan source was produced, and none was written for the new instructions. Re-run from " + "the start rather than resuming a journal that answers a different question."; /** * The record one approved Plan leaves behind. * - * Five facts, retained together because they are one answer: which invocation + * Five facts, saved together because they are one answer: which invocation * asked, which prompt it asked about, the exact bytes that were approved, their * digest, and that the structural admission succeeded. A continuation reads it * back as hostile data — a history is input, not a value this process left in @@ -1004,7 +1004,7 @@ function readArtifact(value: Json): PlanArtifact | undefined { } const UNREADABLE_ARTIFACT = - "the retained Plan artifact cannot be read as one, so no Plan source was produced."; + "the saved Plan artifact cannot be read as one, so no Plan source was produced."; function admitPlan(validate: StructuralValidation): IdentityComponent { return { @@ -1025,7 +1025,7 @@ function admitPlan(validate: StructuralValidation): IdentityComponent { // would be a different record for every draft, so a continuation whose // instructions moved would author a second Plan rather than meet the one // this site already has. - const retained = yield* durablePlanOperation(`plan:artifact:${id}`, function* () { + const saved = yield* durablePlanOperation(`plan:artifact:${id}`, function* () { const validation = yield* validate(candidate); if (validation.outcome === "invalid") { throw new Error( @@ -1042,7 +1042,7 @@ function admitPlan(validate: StructuralValidation): IdentityComponent { }; }); - const artifact = readArtifact(retained); + const artifact = readArtifact(saved); if (artifact === undefined) { throw new StaleInputError(UNREADABLE_ARTIFACT); } diff --git a/packages/cli/src/plan-writer-profile.ts b/packages/cli/src/plan-writer-profile.ts index 0596bb480..41d5c641b 100644 --- a/packages/cli/src/plan-writer-profile.ts +++ b/packages/cli/src/plan-writer-profile.ts @@ -40,7 +40,7 @@ import { installPermissionMode, installPromptFailurePolicy, registerAgentProvider, - retainedSource, + savedSource, useNormalizedOutput, useTerminalOutput, } from "@executablemd/core"; @@ -469,7 +469,7 @@ export function* runPlanCommandDocument(profile: PlanWriterProfile): Operation` is durable work: it returns only once its provider turn has * settled and its ordinary `agent_prompt` record has appended, and that record - * is the only retained truth about it. Between those two moments there is a + * is the only saved truth about it. Between those two moments there is a * turn nobody can read — queued, then streaming, then finished but unpublished * — and a REPL that showed nothing until the append would show nothing for the * whole of the interesting part. @@ -12,7 +12,7 @@ * `Agent.requestPermission()` inside the execution, publishes an immutable * reading of each turn as the provider's own events go past, and removes that * reading at the exact moment the durable record replaces it. Nothing here is - * retained, nothing here is a Core Api member, and nothing here is visible to a + * saved, nothing here is a Core Api member, and nothing here is visible to a * document. * * ## The observer must not become the consumer @@ -181,7 +181,7 @@ export interface ReplAgentReading { * Every Prompt this process observed, live or since published. * * In observation order. A reader presents these rather than concatenating the - * live turns with the retained ones, because a turn that has published is + * live turns with the saved ones, because a turn that has published is * still in the same place it was. */ readonly slots: readonly ReplAgentSlot[]; @@ -787,7 +787,7 @@ export function useReplAgent( // the caller's one transition, and transfers exactly this turn's slot. yield* publication.append(); } catch (error) { - // Nothing was retained, so nothing may still be shown as though it is + // Nothing was saved, so nothing may still be shown as though it is // about to be. The turn this publication began is taken down and the // removal announced before the failure travels on — otherwise a // terminal overlay outlives the record it was waiting for. Its slot diff --git a/packages/cli/src/repl/application.ts b/packages/cli/src/repl/application.ts index cdcfe3125..80ca5db33 100644 --- a/packages/cli/src/repl/application.ts +++ b/packages/cli/src/repl/application.ts @@ -141,22 +141,22 @@ export interface ReplLive { * * Empty wherever a live reading has nothing to say: a replay, a document * with no element in flight, and every frozen prefix. What is here is what - * this process watched happen, which is why retained syntax and a recorded + * this process watched happen, which is why saved syntax and a recorded * admission add nothing to it. */ readonly lifecycle: ReplLifecycleReading; } /** - * One live or retained Sessions turn. Publication changes its source of facts; + * One live or saved Sessions turn. Publication changes its source of facts; * `key` preserves mounted identity and focus across that transition. */ export interface ReplSessionTurn { /** The mounted identity: this process's slot, or a record's own marker. */ readonly key: string; readonly prompt: string; - /** `retained` once a record holds it, and the live states until then. */ - readonly state: ReplLiveTurnState | "retained"; + /** `saved` once a record holds it, and the live states until then. */ + readonly state: ReplLiveTurnState | "saved"; readonly text: string; readonly agent: string | undefined; /** @@ -181,7 +181,7 @@ export interface ReplSessionTurn { /** * Order turns by Prompt scheduling, not publication: concurrent providers can * publish a later Prompt first. Unobserved turns belong to the replayed prefix - * and precede observed slots, whose own scheduling order is retained. + * and precede observed slots, whose own scheduling order is saved. */ function chronology(model: ReplModel, live: ReplLive): readonly ReplSessionTurn[] { const observed = new Set(); @@ -193,7 +193,7 @@ function chronology(model: ReplModel, live: ReplLive): readonly ReplSessionTurn[ const shown: ReplSessionTurn[] = []; for (const turn of model.turns) { if (!observed.has(correlation(turn.entry, turn.name))) { - shown.push(retainedTurn(turn.marker, turn)); + shown.push(savedTurn(turn.marker, turn)); } } for (const slot of [...live.agent.slots].sort((left, right) => left.order - right.order)) { @@ -229,7 +229,7 @@ function resolved( ); if (record !== undefined) { // Keyed by the slot, not by the marker: this is the row it already was. - return retainedTurn(slot.key, record); + return savedTurn(slot.key, record); } // Accounted for here before the history it belongs to was projected. The facts // it had are still the facts, and a row that vanished for this one frame is @@ -254,11 +254,11 @@ function liveTurn(key: string, turn: ReplLiveTurn, live: ReplLive): ReplSessionT }); } -function retainedTurn(key: string, turn: ReplAgentTurn): ReplSessionTurn { +function savedTurn(key: string, turn: ReplAgentTurn): ReplSessionTurn { return Object.freeze({ key, prompt: turn.input, - state: "retained", + state: "saved", text: turn.text, agent: turn.agent, sessionKey: turn.sessionKey.length === 0 ? undefined : turn.sessionKey, @@ -332,7 +332,7 @@ export const EMPTY_FORM: ReplFormState = Object.freeze({ * * Freedom restores the control that was focused before a modal was pushed, which * is where focus *was* rather than where the question now is: answering leaves a - * retained invocation to look at, and dismissing leaves one still being asked. + * saved invocation to look at, and dismissing leaves one still being asked. * Both are named here rather than resolved, because which row that is depends on * what is mounted and only the view knows that. */ @@ -342,7 +342,7 @@ export type ReplFocusRestore = * * Named by what it caused rather than by where it sits: the record appends * after the drawer is gone, so the row does not exist yet at the moment the - * association is recorded. `known` is every answer already retained when this one was + * association is recorded. `known` is every answer already saved when this one was * taken and `answer` is exactly what was sent, which together name one * record — not the newest one, and not an answer somebody else caused. */ @@ -384,7 +384,7 @@ export interface ReplViewports { /** Rows the open permission drawer is scrolled by. */ readonly permission: number; /** - * Rows each retained drawer reading is scrolled by, one per reading. + * Rows each saved drawer reading is scrolled by, one per reading. * * Keyed by what the reading **is** — the execution, the prefix being read, and * the binding or occurrence being shown — rather than by the drawer's title or @@ -482,7 +482,7 @@ export interface ReplView { readonly state: ReplState; readonly model: ReplModel; /** - * Every position the history retained, and what the head is doing. + * Every position the history saved, and what the head is doing. * * The explicit exception to this view being a reading of one prefix: the * band is a map of the whole recorded order, because the positions after @@ -560,7 +560,7 @@ export function answered(state: ReplState, model: ReplModel, answer: Json): Repl // invocation that has it rather than wherever the drawer was opened from. // Which invocation that is cannot be read yet: its record appends after this, // so the claim carries what will identify it when it arrives. - restore: Object.freeze({ kind: "answered", known: retainedAnswers(model), answer }), + restore: Object.freeze({ kind: "answered", known: savedAnswers(model), answer }), route: Object.freeze({ ...state.route, drawers: Object.freeze(state.route.drawers.filter((drawer) => drawer.kind !== "live-elicit")), @@ -1308,7 +1308,7 @@ export function reduceRepl( refusal: undefined, }); } - // A retained reading: History, a binding's value or a recorded answer. + // A saved reading: History, a binding's value or a recorded answer. // Scrolling one changes that reading's own offset and nothing else — no // canonical location, no Journal, no History selection and no answer. const reading = readingKeyOf(state, open); @@ -1707,7 +1707,7 @@ export function focusSettled( if (view.state.restore === undefined || claim === undefined || focused !== claim) { return view.state; } - // The reveal this frame presented, retained before the claim that caused it is + // The reveal this frame presented, saved before the claim that caused it is // spent. Only a claim on a catalog row can have moved that window, and only // the window's own admitted start is kept — the offset this process holds // becomes the one a person is actually looking at, so the next frame asks the @@ -1723,7 +1723,7 @@ export function focusSettled( } /** Every answer this history already holds, wherever in its entries it holds it. */ -function retainedAnswers(model: ReplModel): readonly string[] { +function savedAnswers(model: ReplModel): readonly string[] { const markers: string[] = []; const walk = (scope: ReplScope): void => { for (const elicitation of scope.elicitations) { @@ -2350,7 +2350,7 @@ function described(view: ReplView, context: ReplPresentationContext): DescribedS // Present and empty. This REPL keeps one execution per invocation, and a // Sessions surface that vanished when it held nothing would read as a // feature that does not exist. - items.push(line("sessions:empty", " (none retained)", styleOf("metadata"))); + items.push(line("sessions:empty", " (none saved)", styleOf("metadata"))); } else { // One window over the whole reading. Every conversation, turn, fact, audit // and request is built in order and then windowed: a list that described all @@ -2756,7 +2756,7 @@ function readingRows(prepared: ReplPreparedReading): readonly Described[] { * The selected entry's own, and nothing when a prefix admitted none. Live text * belongs to the entry producing it — the last one this prefix admitted, since * entries are serial — so a reader looking at an earlier entry is given that - * entry's retained reading rather than text from the run that is still going. + * entry's saved reading rather than text from the run that is still going. */ export function readingOf(view: ReplView): ReplReading { const key = view.selection.entry?.key; @@ -2809,7 +2809,7 @@ function transcriptOf(model: ReplModel, selection: ReplSelection): readonly Repl * * Only the last entry a prefix admitted can still be running — entries are * serial, and an earlier one settled before this one was admitted. So output - * this process has not retained belongs there, and nowhere else. Selecting + * this process has not saved belongs there, and nowhere else. Selecting * nothing is reading the execution, which includes whatever is running in it. */ function livesHere(model: ReplModel, selection: ReplSelection): boolean { @@ -2968,7 +2968,7 @@ function sessionRows( line( `sessions:audit:${turn.key}:${at}`, // The outcome comes last and stays, for the same reason a turn's state - // does: it is the whole of what a retained audit says. + // does: it is the whole of what a saved audit says. fitLine( [ { text: " granted: " }, @@ -3138,7 +3138,7 @@ function stateOf(turn: ReplSessionTurn): string { return turn.state === "terminal" ? `${ended}, not recorded yet` : `${ended}, recorded`; } -/** What a retained audit says happened, without repeating the whole record. */ +/** What a saved audit says happened, without repeating the whole record. */ function outcomeOf(audit: ReplAgentPermission): string { if (audit.outcome === "cancelled") { return "cancelled"; @@ -3327,7 +3327,7 @@ function drawerContent( } else if (open.kind === "recorded-elicit") { title = drawerTitle("Recorded answer", open.elicitation.location, width); // The whole of what was asked and the whole of what was answered. A drawer is - // where the retained value is, so a summary here would leave a reader with no + // where the saved value is, so a summary here would leave a reader with no // way to see what the record actually holds. content.push({ described: drawerLine("drawer:schema", "schema", width, styleOf("field-label")), @@ -3378,7 +3378,7 @@ function drawerContent( dismissing = request.key; } else if (open.kind === "history") { title = "History"; - // Every retained position, through the same window. A marker outside it is + // Every saved position, through the same window. A marker outside it is // not described, so it is not a cell, not a target and not a Tab stop — // which is what makes reaching the last one a thing the controls do rather // than something clipping hides. @@ -4148,7 +4148,7 @@ export interface ReplPresentation { } /** - * Which reading one retained drawer is showing, as its own identity. + * Which reading one saved drawer is showing, as its own identity. * * Not the title and not the position in the stack: two bindings called `name` in * different scopes are different readings and must not share a window position, @@ -4400,7 +4400,7 @@ function measurementTextOf(component: ReplComponent, input: ReplView * Whether one row is a control. * * Read from what the row *is* — the component it was described with — rather - * than from how its key happens to be spelled. A turn's own facts, a retained + * than from how its key happens to be spelled. A turn's own facts, a saved * audit, the location somebody copies and the reason the last action changed * nothing are lines, and a line has nothing to activate; every control answers * Enter, so a pointer on one asks for exactly what Enter there asks for. diff --git a/packages/cli/src/repl/entries.ts b/packages/cli/src/repl/entries.ts index 13c7e2ba8..32ad28519 100644 --- a/packages/cli/src/repl/entries.ts +++ b/packages/cli/src/repl/entries.ts @@ -215,7 +215,7 @@ function segment( /** What range of the physical stream one execution may read and write. */ export interface EntrySegmentView { /** The events this entry's range already holds, in append order. */ - readonly retained?: readonly DurableEvent[]; + readonly saved?: readonly DurableEvent[]; /** Whether nothing follows this range in the physical stream. */ readonly final?: boolean; } @@ -228,7 +228,7 @@ export interface EntrySegmentView { * repeated coroutine id from somebody else's. `append()` goes to the physical * stream first and is remembered only once that stream has acknowledged it — * the same order the physical stream itself keeps, and for the same reason: a - * local record of a write that was never retained is a view describing a history + * local record of a write that was never saved is a view describing a history * the file does not hold. After a failed append there is nothing to roll back, * because nothing moved. * @@ -250,7 +250,7 @@ export class EntrySegmentStream implements DurableStream { constructor(physical: DurableStream, view: EntrySegmentView = {}) { this.physical = physical; - this.snapshot = (view.retained ?? []).map(clone); + this.snapshot = (view.saved ?? []).map(clone); this.last = view.final ?? true; // Read off the range rather than taken as a claim: what settles an entry is // its own terminal close, and the records say whether it holds one. diff --git a/packages/cli/src/repl/fitting.ts b/packages/cli/src/repl/fitting.ts index 3fb0354ea..0b8ca4ef5 100644 --- a/packages/cli/src/repl/fitting.ts +++ b/packages/cli/src/repl/fitting.ts @@ -161,7 +161,7 @@ export interface ReplFitGeometry { * cheaper comparison that is already cheap. */ export interface ReplFitStore { - /** The geometry retained for these exact inputs, or none. */ + /** The geometry saved for these exact inputs, or none. */ held( lines: readonly ReplReadingLine[], room: number, @@ -315,9 +315,9 @@ export function* prepareReading( room: number, store?: ReplFitStore, ): Operation> { - const retained = store?.held(lines, room, size); - if (retained !== undefined) { - return Ok(decorate(lines, retained)); + const saved = store?.held(lines, room, size); + if (saved !== undefined) { + return Ok(decorate(lines, saved)); } const measured = yield* fitGeometry(renderer, size, lines, room); if (!measured.ok) { diff --git a/packages/cli/src/repl/history-rail.ts b/packages/cli/src/repl/history-rail.ts index 8d42caedf..88a151562 100644 --- a/packages/cli/src/repl/history-rail.ts +++ b/packages/cli/src/repl/history-rail.ts @@ -1,5 +1,5 @@ /** - * The History band: every retained position, on five rows of measured rail. + * The History band: every saved position, on five rows of measured rail. * * A map of the whole recorded order, not of the part a reader has reached. * The selection sits on it in amber; the head sits below its end. Content diff --git a/packages/cli/src/repl/journal.ts b/packages/cli/src/repl/journal.ts index bfa304a78..b0adbd246 100644 --- a/packages/cli/src/repl/journal.ts +++ b/packages/cli/src/repl/journal.ts @@ -44,35 +44,35 @@ export class ReplJournalError extends Error { } /** - * An append-only stream that says when a record has actually been retained. + * An append-only stream that says when a record has actually been saved. * * The interface rather than the class, so a test injects an in-memory one and * the same corpus runs under all three runtimes without an operator's * filesystem being part of what is under test. */ -export interface RetainedStream extends DurableStream { - /** Called after each append has been retained, with what was retained. */ +export interface SavedStream extends DurableStream { + /** Called after each append has been saved, with what was saved. */ onAppend: ((event: DurableEvent) => void) | null; } /** - * One execution's retained stream. + * One execution's saved stream. * * The events are held in memory because this process is the only writer: what is * in hand is what the file holds. An append reaches the file first and is only * then acknowledged — remembered and announced — so nothing downstream ever sees * a record that a crash would have lost. */ -export class RetainedReplStream implements RetainedStream { +export class SavedReplStream implements SavedStream { readonly path: string; private events: DurableEvent[]; /** Called after each append has reached the file, with what reached it. */ onAppend: ((event: DurableEvent) => void) | null = null; - constructor(path: string, retained: readonly DurableEvent[] = []) { + constructor(path: string, saved: readonly DurableEvent[] = []) { this.path = path; - this.events = retained.map(clone); + this.events = saved.map(clone); } // deno-lint-ignore require-yield @@ -93,7 +93,7 @@ export class RetainedReplStream implements RetainedStream { /** One execution the REPL can show: an opaque name and the stream behind it. */ export interface ReplExecution { readonly id: string; - readonly stream: RetainedStream; + readonly stream: SavedStream; } /** @@ -156,7 +156,7 @@ export function* createExecutionFile(root: string, execution: string): Operation throw new ReplJournalError("this execution's history could not be created.", { cause: error }); } - return { id: execution, stream: new RetainedReplStream(path) }; + return { id: execution, stream: new SavedReplStream(path) }; } /** @@ -176,7 +176,7 @@ export function* openExecutionFile(root: string, execution: string): Operation(value: T): Readonly { } /** - * One retained value, copied out of the event graph and frozen. + * One saved value, copied out of the event graph and frozen. * * Copied rather than frozen in place, because the events belong to whoever * handed them over. Freezing a value inside them would reach back out of this diff --git a/packages/cli/src/repl/navigation.ts b/packages/cli/src/repl/navigation.ts index 305a5c84c..da32efebe 100644 --- a/packages/cli/src/repl/navigation.ts +++ b/packages/cli/src/repl/navigation.ts @@ -1,5 +1,5 @@ /** - * Every position this history retained, and what the execution is doing now. + * Every position this history saved, and what the execution is doing now. * * The one thing on this screen that is *not* read at the selected prefix. A * reader standing at an earlier position is still owed the whole rail — the @@ -33,13 +33,13 @@ import type { ReplCheckpoint } from "./model.ts"; -/** One retained position, as the rail needs to know it. */ +/** One saved position, as the rail needs to know it. */ export type ReplNavigationPoint = Pick; /** * What the execution is doing, as the band says it. * - * Seven states and no eighth. Each is a fact about retained records and about + * Seven states and no eighth. Each is a fact about saved records and about * work this process actually owns — never about what a body returned, what a * root Close recorded, what a badge says or what the selected content shows. */ @@ -85,9 +85,9 @@ export function navigationOf( /** What the head is doing, by the one precedence the band states. */ export interface ReplHeadFacts { - /** Whether this prefix retained any entry at all. */ + /** Whether this prefix saved any entry at all. */ readonly entries: number; - /** Whether the latest entry has a retained root outcome. */ + /** Whether the latest entry has a saved root outcome. */ readonly outcome: boolean; /** * Whether this process still owns the entry's work. @@ -110,13 +110,13 @@ export interface ReplHeadFacts { * In precedence order, because more than one can be true at once and the * first match is the honest one: * - * 1. an outcome is retained and the work is still coming down — **settling**, + * 1. an outcome is saved and the work is still coming down — **settling**, * which is the window a reader watching a run end is actually in; * 2. no outcome and the controller is paused — **paused**; * 3. no outcome and it is pausing — **pausing**, distinct because the holds * have not all been taken yet; * 4. work this process owns, and none of the above — **live**; - * 5. nothing retained and nothing owned — **empty**; + * 5. nothing saved and nothing owned — **empty**; * 6. no owned work and the latest entry has an outcome — **settled**, and * neutrally: a success, a failure and a cancellation all finished; * 7. no owned work and no outcome — **unfinished**. Never an inferred pause: @@ -153,7 +153,7 @@ export const HEAD_LABELS: Readonly> = Object.freez }); /** - * What one retained position is, said as a category and nothing more. + * What one saved position is, said as a category and nothing more. * * The drawer's row for a marker. `N` is its one-based ordinal in the full * list and `E` is the admitted order of the entry it belongs to — both @@ -182,7 +182,7 @@ export function pointLabel(point: ReplNavigationPoint, ordinal: number, entry: n } /** - * Every retained position with the entry it belongs to. + * Every saved position with the entry it belongs to. * * The entry number is the count of `entry` kinds up to and including this * point, which is the admitted order — counted from the list rather than diff --git a/packages/cli/src/repl/presentation-style.ts b/packages/cli/src/repl/presentation-style.ts index 97c2f8313..9ffe8c3c8 100644 --- a/packages/cli/src/repl/presentation-style.ts +++ b/packages/cli/src/repl/presentation-style.ts @@ -74,7 +74,7 @@ export type ReplPresentationRole = | "field-value" /** The line the next entry is typed on. */ | "draft" - /** A retained position, and the band that holds them. */ + /** A saved position, and the band that holds them. */ | "history" /** The rail beside a reading, saying which region an element occupies. */ | "rail-pending" @@ -105,7 +105,7 @@ export type ReplPresentationRole = * One row's presentation facts, as the reading settles them. * * `selected` is what the resolved route is pointing at; `inspected` is whether - * this reading is a retained position rather than the live head. Both are facts + * this reading is a saved position rather than the live head. Both are facts * about the model, so both survive focus moving away. */ export interface ReplRowStyle { @@ -170,7 +170,7 @@ export const REPL_PALETTE = Object.freeze({ focus: 0x7fd3e8, /** Behind a selected row, for the whole width the frame measured it at. */ selectedSurface: 0x122026, - /** A retained reading, and the band that holds the positions. */ + /** A saved reading, and the band that holds the positions. */ historical: 0xc9a86a, historySurface: 0x08090b, draftSurface: 0x0a0d0f, @@ -230,7 +230,7 @@ export const REPL_PALETTE = Object.freeze({ historyMinorEarlier: 0x7fd3e8, historyEntryLater: 0x5d666e, historyMinorLater: 0x4e565d, - /** The retained position being read: its diamond, junction and caption. */ + /** The saved position being read: its diamond, junction and caption. */ historySelected: 0xc9a86a, /** The head, when this process is running something. */ historyHeadLive: 0x7fd3e8, @@ -269,7 +269,7 @@ export function textStyleOf(style: ReplRowStyle, focused: boolean): ReplTextStyl * The foreground one run of a row's text takes. * * The run's own role decides the colour and the weight; the row it belongs to - * decides whether a retained reading is being marked, which is a fact about the + * decides whether a saved reading is being marked, which is a fact about the * row rather than about any of its characters. */ export function runStyleOf( @@ -321,7 +321,7 @@ export function terminalColour(colour: number): number { } function colourOf(role: ReplPresentationRole, inspected: boolean): number { - // A retained reading is marked on the two rows that say which reading it is, + // A saved reading is marked on the two rows that say which reading it is, // and nowhere else: accenting every source line of a historical prefix would // say the document had changed rather than that the position had. if (inspected && (role === "status" || role === "history")) { diff --git a/packages/cli/src/repl/program.ts b/packages/cli/src/repl/program.ts index 9e46e520c..21fe11049 100644 --- a/packages/cli/src/repl/program.ts +++ b/packages/cli/src/repl/program.ts @@ -169,7 +169,7 @@ export function* runReplProgram(options: ReplProgramOptions): Operation> { const projected = projectRepl(yield* execution.stream.readAll(), state.route.at); diff --git a/packages/cli/src/repl/renderer.ts b/packages/cli/src/repl/renderer.ts index 6861dd990..ab0416f76 100644 --- a/packages/cli/src/repl/renderer.ts +++ b/packages/cli/src/repl/renderer.ts @@ -75,7 +75,7 @@ export interface ReplTarget { readonly bounds: ReplBounds; } -/** Where everything landed. Immutable, and retained with its frame. */ +/** Where everything landed. Immutable, and saved with its frame. */ export interface ReplFrameMap { /** In draw order, so a later entry is drawn over an earlier one. */ readonly targets: readonly ReplTarget[]; diff --git a/packages/cli/src/repl/route.ts b/packages/cli/src/repl/route.ts index 9d72ecc26..8d8eb23dd 100644 --- a/packages/cli/src/repl/route.ts +++ b/packages/cli/src/repl/route.ts @@ -142,10 +142,10 @@ export interface ReplSelection { /** The innermost selected scope, which is the entry when only it was named. */ readonly scope: ReplScope | undefined; /** - * The retained conversation the filter names, when the history holds it. + * The saved conversation the filter names, when the history holds it. * * None where no filter was asked for, and none where the key names a live - * conversation that has settled nothing yet — there is no retained group to + * conversation that has settled nothing yet — there is no saved group to * point at until one of its turns is appended. */ readonly session: ReplAgentSession | undefined; @@ -428,7 +428,7 @@ export function resolveLocation( * * At the live head a key may be one this process has started and nothing has * settled yet, which no history holds and only the process can say. At a - * historical position it may only be one that prefix retained: a conversation + * historical position it may only be one that prefix saved: a conversation * that started after the position a reader is looking at had not happened yet, * and answering with it would show the present as the past. * @@ -443,9 +443,9 @@ function selectConversation( if (route.session === undefined) { return Ok(undefined); } - const retained = model.sessions.find((session) => session.sessionKey === route.session); - if (retained !== undefined) { - return Ok(retained); + const saved = model.sessions.find((session) => session.sessionKey === route.session); + if (saved !== undefined) { + return Ok(saved); } if (route.at === undefined && live.sessions.includes(route.session)) { return Ok(undefined); @@ -502,7 +502,7 @@ function openDrawer( } if (!live.permission) { // The same reason a live question's drawer cannot be reopened: a request - // that is still waiting is not in any history, so every retained fact is + // that is still waiting is not in any history, so every saved fact is // consistent with none ever arriving. Only the process holding one — and // holding the exact one this drawer opens over — may say so. return Err( @@ -561,7 +561,7 @@ function openDrawer( ); } if (!live.elicit) { - // Nothing retained can establish this drawer. A waiting question is the + // Nothing saved can establish this drawer. A waiting question is the // one thing a Journal never holds — it records answers — so `settled`, // the recorded elicitations and every other fact in the model are all // consistent with no question ever arriving. A route resolved here diff --git a/packages/cli/src/repl/session.ts b/packages/cli/src/repl/session.ts index b1b91e105..45c64cff1 100644 --- a/packages/cli/src/repl/session.ts +++ b/packages/cli/src/repl/session.ts @@ -4,7 +4,7 @@ * Both directions arrive here. A **fresh** session submits one entry into an * empty history; a **cold** one opens a history somebody else wrote and reaches * the same place through ordinary replay. They share this module because they - * are the same thing: a retained stream, an execution over it, and a model + * are the same thing: a saved stream, an execution over it, and a model * reprojected from what that stream now holds. * * ## The entry is admitted once, or not at all @@ -19,7 +19,7 @@ * * A session owns the one physical stream, installs its one observer on it, and * owns **at most one** entry task. Starting another entry needs two separate - * facts: the entry before it has a retained terminal close, and that entry's own + * facts: the entry before it has a saved terminal close, and that entry's own * task has finished tearing down and been joined. Observing a terminal outcome * is not the second of those — an execution whose root has closed is still * releasing providers, middleware and held permissions — so a submission that @@ -41,9 +41,9 @@ * runs in a scope the caller can drop: a divergence, a stale answer or a * malformed record comes back as a refusal, and the application mounts that * instead of a half-reconstructed view. Nothing was appended and no provider was - * called, because replay contacts neither until it is past what was retained. + * called, because replay contacts neither until it is past what was saved. * - * Admission is the first thing that proves replay got past the retained prefix: + * Admission is the first thing that proves replay got past the saved prefix: * the execution settling, the first new record reaching the file, or the first * question reaching this process. Before any of those, a divergence class is a * refusal; after them, a failure is an outcome the transcript shows. @@ -241,7 +241,7 @@ export interface ExpansionView { export interface ReplReading { /** The model of the requested prefix. */ readonly model: ReplModel; - /** Every position the whole file retained, and what the head is doing. */ + /** Every position the whole file saved, and what the head is doing. */ readonly navigation: ReplHistoryNavigation; } @@ -287,7 +287,7 @@ export interface ReplSession { /** The frozen model of the projected prefix, as it stands. */ readonly model: ReplModel; /** - * Every position the whole history retained, and what the head is doing. + * Every position the whole history saved, and what the head is doing. * * The one reading not taken at the selected prefix. A reader standing at an * earlier position is owed the whole rail — the positions after theirs are @@ -343,7 +343,7 @@ export interface ReplSession { * * Ephemeral and detached, like the Agent reading beside it: a cold process * reconstructs none of it, and an entry that closes takes its observations - * with it. Static syntax and a retained admission prove nothing about it — + * with it. Static syntax and a saved admission prove nothing about it — * what is here is what this process actually watched happen. */ readonly lifecycle: ReplLifecycleReading; @@ -366,7 +366,7 @@ export interface ReplSession { /** * Admit one more entry into this execution, or refuse and start nothing. * - * Answers once that entry's work has reached past what the history retained — + * Answers once that entry's work has reached past what the history saved — * its first record, its first question or its own settlement — so a caller that * is told `Ok` has an entry, not an intention. A refusal names why this is not * a moment to submit and leaves the history, the running entry and the proposed @@ -430,7 +430,7 @@ interface ReplEntryTask { /** The admission-order key of the entry this task is running. */ readonly key: string; readonly task: Task; - /** Resolves once this entry's work has passed what its segment retained. */ + /** Resolves once this entry's work has passed what its segment saved. */ readonly admitted: Operation>; /** Resolves with how this entry's execution finished. */ readonly finished: Operation>; @@ -557,12 +557,12 @@ function* start( /** How the last entry to finish finished. */ let finished: Result | undefined; /** - * Every retained position, taken at the whole file rather than at the + * Every saved position, taken at the whole file rather than at the * selected prefix. Rebuilt with the model, so a wake that moves one moves * both. */ let points: readonly ReplNavigationPoint[] = NO_NAVIGATION.checkpoints; - /** Whether the latest entry of the *whole* file has a retained outcome. */ + /** Whether the latest entry of the *whole* file has a saved outcome. */ let outcome = false; /** How many entries the whole file holds. */ let admitted = 0; @@ -597,7 +597,7 @@ function* start( */ function readingAt(at: string | undefined): Result { // One acknowledged array answers both halves. The stream is the one - // writer, so `retained` is what the file holds; projecting the whole of + // writer, so `saved` is what the file holds; projecting the whole of // it and the requested prefix from the same slice is what makes the // rail and the content incapable of describing different files. // @@ -606,8 +606,8 @@ function* start( // usually an overlay moving rather than a record arriving. The head is // *not* memoized: pausing, resuming and the last of an entry's // resources being released all change it while the file stands still. - if (memo === undefined || memo.length !== retained.length || memo.at !== at) { - memo = { length: retained.length, at, projected: project(retained.slice(), at) }; + if (memo === undefined || memo.length !== saved.length || memo.at !== at) { + memo = { length: saved.length, at, projected: project(saved.slice(), at) }; } if (!memo.projected.ok) { return memo.projected; @@ -754,7 +754,7 @@ function* start( } /** - * Whichever entry is running has reached past what its segment retained. + * Whichever entry is running has reached past what its segment saved. * * The same fact admits the session and answers the submission that started * the entry: both are waiting to hear that replay got beyond the records @@ -802,7 +802,7 @@ function* start( ), ); } - const prefix = projectRepl(retained); + const prefix = projectRepl(saved); if (!prefix.ok) { return prefix; } @@ -915,7 +915,7 @@ function* start( }); const outcome = yield* runEntry(key, source, view, initialBindings); settle(outcome); - // A settled entry is past whatever its segment retained, whichever way + // A settled entry is past whatever its segment saved, whichever way // it settled — including a failure that appended nothing at all, which // is still an answer to the submission that asked for it. admit(outcome.ok ? Ok(undefined) : Err(outcome.error)); @@ -1013,13 +1013,13 @@ function* start( // The stream is the one writer, so what it acknowledges is what the file // holds. Reprojection reads that acknowledged array rather than // accumulating a second copy of the history beside it. - const retained = yield* stream.readAll(); + const saved = yield* stream.readAll(); // The positions this history already holds, before anything is appended. // `start` validated the same bytes; this derives the thin summary from // them so a session that never appends still shows its rail. reproject(); const observe = (event: DurableEvent): void => { - retained.push(event); + saved.push(event); // One transition, and nothing suspends inside it: the record joins the // history, the live overlay it completed is removed, and only then does // anything announce. No observable snapshot holds one turn twice, and @@ -1070,7 +1070,7 @@ function* start( return question !== undefined; })(elicitation.changes); - // A queued Agent turn is work beyond the retained prefix, exactly as a new + // A queued Agent turn is work beyond the saved prefix, exactly as a new // record or a question is: replay that reached one is past what the history // held, so the session is admitted rather than still provisional. yield* spawn(consumeAdmissions(agentReadings, reached)); @@ -1100,7 +1100,7 @@ function* start( begin( resumed.entry.key, resumed.entry.source, - new EntrySegmentStream(stream, { retained: resumed.segment.events, final: true }), + new EntrySegmentStream(stream, { saved: resumed.segment.events, final: true }), entryInitialBindings(head, resumed.entry), ); } else if (submitted !== undefined) { @@ -1205,7 +1205,7 @@ function declaredMarkdown( * document failed". * * A closed list of the protocol's own refusals. A document that fails after - * replay is past the retained prefix is an outcome the transcript shows; one of + * replay is past the saved prefix is an outcome the transcript shows; one of * these means the run never got there, and what a reader would otherwise be * shown is a reconstruction of something that did not happen. */ diff --git a/packages/cli/src/repl/source-reading.ts b/packages/cli/src/repl/source-reading.ts index 00f4bc747..372b84c90 100644 --- a/packages/cli/src/repl/source-reading.ts +++ b/packages/cli/src/repl/source-reading.ts @@ -18,7 +18,7 @@ * ## Static syntax is not execution * * `inspectSource` says where the executable elements of a text are written. It - * says nothing about whether any of them ran: a retained prefix shows the same + * says nothing about whether any of them ran: a saved prefix shows the same * elements it always showed, and a cold process reconstructs no phase at all. * So an element acquires a phase only from an observation actually attributed * to it — matched on this entry, its lineage and its recorded source position — @@ -166,11 +166,11 @@ function caption(key: string, section: ReplReadingSection): ReplReadingLine { /** What a reading is being built for. */ export interface ReplReadingRequest { readonly entry: ReplEntry; - /** This session's observations, or an empty reading for a retained prefix. */ + /** This session's observations, or an empty reading for a saved prefix. */ readonly lifecycle: ReplLifecycleReading; /** Text a run still going has emitted, or empty. */ readonly live: string; - /** Whether this reading is a retained position rather than the live head. */ + /** Whether this reading is a saved position rather than the live head. */ readonly inspected: boolean; } @@ -193,7 +193,7 @@ export function entryReading(request: ReplReadingRequest): ReplReading { * * Four readings, and exactly one of them is true at a time. Live text is * labelled as live because it is still arriving and the reader must not take a - * partial document for a finished one; a retained final result replaces it once + * partial document for a finished one; a saved final result replaces it once * and stops being labelled. A root that settled having rendered nothing is not * an absence of information — it is the outcome, which is said instead. */ @@ -580,7 +580,7 @@ class Build { * What has actually been observed of this element, or nothing. * * Matched on the entry's own lifecycle reading, the recorded source position - * and the fragment that position belongs to. A retained prefix has no + * and the fragment that position belongs to. A saved prefix has no * observations at all, so every element in it reads as unobserved — which is * the truth: static syntax does not prove execution. */ diff --git a/packages/cli/src/session-coordinator.ts b/packages/cli/src/session-coordinator.ts index a090b9023..6e052fffa 100644 --- a/packages/cli/src/session-coordinator.ts +++ b/packages/cli/src/session-coordinator.ts @@ -65,7 +65,7 @@ export function useSessionRouteStore(): AgentSessionRouteStore | undefined { * How this host observes which build of an agent executable it would run. * * Built here, beside the coordinator, and handed straight to the provider. It - * is not a contextual Api: executable validation decides which retained history + * is not a contextual Api: executable validation decides which saved history * may be accepted, and a decision document middleware could replace could point * the observation at one binary while the run spawns another. */ diff --git a/packages/cli/src/upgrade.ts b/packages/cli/src/upgrade.ts index 5a6fe81ca..2e8906739 100644 --- a/packages/cli/src/upgrade.ts +++ b/packages/cli/src/upgrade.ts @@ -34,7 +34,7 @@ import { Err, Ok, scoped } from "effection"; import type { Operation, Result } from "effection"; import { forEach } from "@effectionx/stream-helpers"; -import { retainedSource, useNormalizedOutput } from "@executablemd/core"; +import { savedSource, useNormalizedOutput } from "@executablemd/core"; import type { IdentityComponent } from "@executablemd/core/host"; import { executeInstalled } from "@executablemd/core/host"; import type { DurableStream } from "@executablemd/durable-streams"; @@ -223,7 +223,7 @@ export function* runUpgrade(run: UpgradeRun): Operation> { const execution = yield* executeInstalled( { - ...retainedSource(UPGRADE_COMMAND_IDENTITY, source), + ...savedSource(UPGRADE_COMMAND_IDENTITY, source), stream: run.stream, // No repository component search. What the document may name is what // this command declares, so a file in the caller's tree cannot answer diff --git a/packages/cli/src/workflow-agent.ts b/packages/cli/src/workflow-agent.ts index 297cf4506..521f46209 100644 --- a/packages/cli/src/workflow-agent.ts +++ b/packages/cli/src/workflow-agent.ts @@ -47,7 +47,7 @@ * The override is qualified: an embedded provider never falls through. A * snapshot that cannot be verified, materialized or launched refuses that agent * rather than quietly becoming the published adapter, which would produce a run - * that completed every Prompt and retained nothing, and said nothing about why — + * that completed every Prompt and saved nothing, and said nothing about why — * the exact silence this feature exists to remove. * * Materialization is lazy, and happens only for an embedded selection, just @@ -60,13 +60,13 @@ * quietly went to the wrong binary. * * A Prompt whose adapter names no turn is not a failed Prompt. It is journaled, - * retained and replayed exactly as it always was; the only thing missing is the + * saved and replayed exactly as it always was; the only thing missing is the * association. * * ## Where a Prompt lands in that conversation * * The session row says which conversation this run is having. Which turn each - * Prompt was is a second fact, and it is retained the same way: in the run's own + * Prompt was is a second fact, and it is saved the same way: in the run's own * database, in the transaction that appends that Prompt. This module installs * the publisher that opens it, because this is the only side that holds both the * run and the placement that made the session — the provider names the turn, the @@ -81,7 +81,7 @@ import { createAcpxProvider, createAcpxSessionStore, DEFAULT_AGENT_NAME, - retainedSession, + savedSession, } from "@executablemd/acp"; import type { AcpxProviderDependencies, @@ -121,7 +121,7 @@ import type { WorkflowAgentAttachment, } from "@executablemd/workflow/deno"; -/** How this host names itself in a retained provider-session record. */ +/** How this host names itself in a saved provider-session record. */ const PROVIDER = "acpx"; /** What kind of durable identity this provider asserts. Tagged, so it compares as one. */ @@ -171,7 +171,7 @@ export const WORKFLOW_SESSION_INSTRUCTIONS = const ALLOWED_TOOLS: readonly string[] = []; /** - * The fingerprint one session policy is retained under. + * The fingerprint one session policy is saved under. * * Over the values that decide the ceiling, so changing any of them is a changed * policy and refuses a session created under the previous one. @@ -231,7 +231,7 @@ export interface WorkflowAgentProfileOptions { */ function assertionsFor(store: AcpxSessionStore) { return function* (placementKey: string): Operation { - const held = yield* retainedSession(store, placementKey); + const held = yield* savedSession(store, placementKey); if (held?.agentSessionId === undefined) { return []; } @@ -258,7 +258,7 @@ function sessionPolicy( policy: string, store: AcpxSessionStore, adapters: EmbeddedAdapters, -): { policy: AcpxSessionPolicy; retainedSessionKey: (placementKey: string) => string | undefined } { +): { policy: AcpxSessionPolicy; savedSessionKey: (placementKey: string) => string | undefined } { const assertions = assertionsFor(store); const emptied = new Set(); /** What each placement key was placed for, so the commit names the same thing. */ @@ -275,8 +275,8 @@ function sessionPolicy( * snapshot on another host would compare as a different agent. * * For every other agent: the resolved command, exactly as this profile - * retained it before any adapter was embedded. Its identity is not this - * host's to restate, and rewriting it would make sessions retained by an + * saved it before any adapter was embedded. Its identity is not this + * host's to restate, and rewriting it would make sessions saved by an * earlier run of the same workflow compare as a different agent — a * compatibility break in return for nothing, since a Gemini session has no * snapshot for the alternative to name. @@ -299,12 +299,12 @@ function sessionPolicy( * What this run retains the session behind one ACPX placement key under. * * A lookup of what the placement recorded, never a parse of the key. The - * placement key namespaces the provider and command into the retained key + * placement key namespaces the provider and command into the saved key * because ACPX's store is shared; recovering one from the other by taking the * spelling apart would be reading arrangement as identity, and would keep * working right up until the two namespaces diverged. */ - function retainedSessionKey(placementKey: string): string | undefined { + function savedSessionKey(placementKey: string): string | undefined { const identity = placed.get(placementKey); return identity === undefined ? undefined : agentSessionKey(identity); } @@ -358,7 +358,7 @@ function sessionPolicy( *established(placement, providerIdentity) { // The order is the contract: the provider has been created and has // asserted, and only now does the mapping commit. An attempt that stops - // before this leaves nothing retained, and the next attachment reconciles + // before this leaves nothing saved, and the next attachment reconciles // it from the provider's own assertion. const identity = placed.get(placement.sessionKey); if (identity === undefined) { @@ -377,11 +377,11 @@ function sessionPolicy( const sessionKey = agentSessionKey(identity); const committed = yield* transactAgentSessions(database, function* (sessions) { - const retained = sessions.read(sessionKey); + const saved = sessions.read(sessionKey); // Re-resolved inside the transaction that commits it, so a mapping // another attempt wrote in between is compared rather than overwritten. - const resolution = resolveAgentSession(retained, policy, [asserted], identity); - if (retained === undefined && resolution.kind === "reattach") { + const resolution = resolveAgentSession(saved, policy, [asserted], identity); + if (saved === undefined && resolution.kind === "reattach") { sessions.commit(resolution.record); } }); @@ -391,7 +391,7 @@ function sessionPolicy( }, }; - return { policy: acpxPolicy, retainedSessionKey }; + return { policy: acpxPolicy, savedSessionKey }; } /** Install the workflow Agent profile for one live or partial attachment. */ @@ -458,7 +458,7 @@ export function* useWorkflowAgentProfile(options: WorkflowAgentProfileOptions): yield* useAgentPromptPublisher( createWorkflowPromptPublisher({ database: options.attachment.database, - retainedSessionKey: sessions.retainedSessionKey, + savedSessionKey: sessions.savedSessionKey, }), ); yield* registerAgentProvider(PROVIDER, factory); diff --git a/packages/cli/src/workflow-bundle.ts b/packages/cli/src/workflow-bundle.ts index ff9e5a605..a7e672c8b 100644 --- a/packages/cli/src/workflow-bundle.ts +++ b/packages/cli/src/workflow-bundle.ts @@ -13,7 +13,7 @@ * * The declaration is authored beside the root and read from beside it: each * path is resolved against the root's own directory, read as bytes, and - * retained with the run. So the whole procedure — root and components — is one + * saved with the run. So the whole procedure — root and components — is one * immutable set of bytes from the moment the run becomes durable, and a working * tree with uncommitted edits runs what it says rather than what its last * commit said. @@ -23,7 +23,7 @@ * bundle, and the source hash of the bytes that were read. The **execution** * view is the same entries plus that source as text, which is what canonical * core resolves the names against. Because the hash is identity, changing what - * a component says changes the definition rather than changing what a retained + * a component says changes the definition rather than changing what a saved * definition executes. */ diff --git a/packages/cli/src/workflow-definition.ts b/packages/cli/src/workflow-definition.ts index 4bbe50da4..90e701519 100644 --- a/packages/cli/src/workflow-definition.ts +++ b/packages/cli/src/workflow-definition.ts @@ -2,7 +2,7 @@ * What a workflow run is a run of, established from the bytes the caller named. * * `xmd workflow start notes.md` names a file, and that file's current bytes are - * what the run is of. They are read once, hashed, and retained with the run + * what the run is of. They are read once, hashed, and saved with the run * before it becomes durable — so a file outside a repository, an untracked * file, and a file edited since its last commit all start, and all start from * what they actually say. @@ -31,7 +31,7 @@ import { fileSource, inspectDocument, parseMarkdownDefinition, - retainedSource, + savedSource, } from "@executablemd/core"; import type { DocumentInfo, FileRootDocument } from "@executablemd/core"; import type { WorkflowBundleComponent } from "@executablemd/core/host"; @@ -72,12 +72,12 @@ export interface EstablishedDefinition { } /** The sources one execution runs: the root, and the bundle it is closed over. */ -export interface RetainedSources { +export interface SavedSources { readonly source: string; readonly components: readonly WorkflowBundleComponent[]; } -/** A definition that cannot be established, or a retained one that cannot be loaded. */ +/** A definition that cannot be established, or a saved one that cannot be loaded. */ export class WorkflowDefinitionUnavailableError extends Error { override name = "WorkflowDefinitionUnavailableError"; } @@ -195,7 +195,7 @@ export function* establishDefinition(reference: string): Operation --at= ` says: run *this* * document, but start it where that run had already got to. That only holds if * the candidate definition, its component bundle and the fork's props produce - * exactly the retained prefix when replayed. Anything else — an effect in a + * exactly the saved prefix when replayed. Anything else — an effect in a * different order, an effect that is no longer there, a document that finishes * early — is divergence, and a fork admitted on divergent history would replay * work its own document never describes. @@ -30,7 +30,7 @@ * * A durable operation performs its work and *then* appends, so a stream that * merely refuses appends would already have run the first live effect. What - * stops this replay is the retained prefix running out: a guard counts the + * stops this replay is the saved prefix running out: a guard counts the * events replay consumes and, on the last one, ends the execution before the * stored result is handed back. Nothing after the checkpoint is entered, no * host service is reached, and no event is appended. @@ -52,13 +52,13 @@ import { Err, Ok, scoped } from "effection"; import type { Operation, Result } from "effection"; import { ReplayGuard } from "@executablemd/durable-streams"; import type { DurableEvent, DurableStream, ReplayOutcome } from "@executablemd/durable-streams"; -import { retainedSource } from "@executablemd/core"; +import { savedSource } from "@executablemd/core"; import { offering, useNoGeneratedEvaluation } from "./workflow-generated.ts"; import { forkJournal, forkRunRecordEvent, isRootImportEvent, - retainedWorkflowInstallation, + savedWorkflowInstallation, selectForkPrefix, workflowBundleInstallation, WorkflowLifecycle, @@ -317,7 +317,7 @@ function* replayPrefix( * One preflight execution of the candidate definition. * * The same shape both passes use: the fork's own identity, the candidate's - * retained source and bundle, and a stream that answers with whatever that pass + * saved source and bundle, and a stream that answers with whatever that pass * is replaying. */ function execution( @@ -327,7 +327,7 @@ function execution( around: (operation: Operation) => Operation, ): WorkflowExecution { return { - root: retainedSource(request.creation.definition.entrypoint, request.established.source, { + root: savedSource(request.creation.definition.entrypoint, request.established.source, { ...(request.creation.definition.targetPath === undefined ? {} : { target: request.creation.definition.targetPath }), @@ -335,7 +335,7 @@ function execution( props: request.creation.props, stream, installations: [ - retainedWorkflowInstallation(run), + savedWorkflowInstallation(run), ...(request.established.components.length === 0 ? [] : [workflowBundleInstallation(request.established.components)]), @@ -380,7 +380,7 @@ function reachedCheckpoint(error: unknown): boolean { * matched, so that is the boundary a caller has to look at. The failure's own * message is carried because it is the durable machinery's account of the * disagreement, and it is about the candidate document rather than about - * retained values. + * saved values. */ function divergence( error: Error, @@ -400,7 +400,7 @@ function divergence( /** * The inherited event replay was about to consume. * - * Its retained id and the effect's own type, which is its identity. The + * Its saved id and the effect's own type, which is its identity. The * evaluated arguments and the recorded result are filtered history and stay * where they are. */ diff --git a/packages/cli/src/workflow-management.ts b/packages/cli/src/workflow-management.ts index 832a9511e..4361f0d95 100644 --- a/packages/cli/src/workflow-management.ts +++ b/packages/cli/src/workflow-management.ts @@ -3,7 +3,7 @@ * `export` — everything a run's caller does without running it. * * `status` and `history` each take one of two sources. A run id addresses this - * host's retained storage; `--artifact=` addresses a sealed file, + * host's saved storage; `--artifact=` addresses a sealed file, * which is completely verified before any of it is presented. The two answer * with the same lifecycle and history values, and the artifact forms add which * artifact answered and the boundary it was sealed at. @@ -21,7 +21,7 @@ * * ## Two projections of one answer * - * `--json` is the contract. It writes the retained values structurally — every + * `--json` is the contract. It writes the saved values structurally — every * event, its exact id, its associated Workspace root and its optional authored * source — so a caller parsing it sees what the run holds rather than a * rendering of it. Human output is a presentation over the same values: it may @@ -193,7 +193,7 @@ function json(value: unknown): string { /** * Which file answered, which artifact it holds, and the boundary it was sealed - * at — above the same rendering a retained run gets. + * at — above the same rendering a saved run gets. * * The path is here and only here. It is where this caller found the evidence, * which is a fact about the invocation rather than about the artifact, so it @@ -211,10 +211,10 @@ function renderArtifact(path: string, artifact: WorkflowArtifactIdentity): strin } /** - * The lines every snapshot has, and the retrieval line only a retained run does. + * The lines every snapshot has, and the retrieval line only a saved run does. * * `retrieval` arrives as its own argument rather than off the snapshot, because - * it is not a member of the shared inspection shape: a retained run has one and + * it is not a member of the shared inspection shape: a saved run has one and * an artifact has none, and reading it from the value would make this renderer * expect a member half its callers cannot supply. */ @@ -283,12 +283,12 @@ function renderList(snapshots: readonly WorkflowLifecycleSnapshot[]): string { } /** - * The retained events, and — when the caller asked — whether each is a + * The saved events, and — when the caller asked — whether each is a * checkpoint a fork may select. * * `--forkable` adds two columns and removes nothing: a caller reading history * to choose a checkpoint needs to see the events it cannot select as much as - * the ones it can. The columns carry stable codes and retained event ids and + * the ones it can. The columns carry stable codes and saved event ids and * nothing else, so an unforkable Agent turn says `agent-state-unavailable` * rather than repeating what the provider said about the session. */ diff --git a/packages/cli/src/workflow.ts b/packages/cli/src/workflow.ts index 759cfa7be..ac109a246 100644 --- a/packages/cli/src/workflow.ts +++ b/packages/cli/src/workflow.ts @@ -1,5 +1,5 @@ /** - * `xmd workflow` — running a document as a retained workflow run, and reading + * `xmd workflow` — running a document as a saved workflow run, and reading * one back. * * The command selects the environment; the document describes the procedure. @@ -40,7 +40,7 @@ * `start` names a document; every other action names a run. That asymmetry is * the whole lifecycle rule: a document path locates a definition and never * selects a previous run, and a run id addresses a run whose definition and - * props are already retained. Starting the same document twice without `--id` therefore + * props are already saved. Starting the same document twice without `--id` therefore * makes two runs, and `resume` accepts no definition, no props and no generated * prop arguments at all — supplying them would ask this run to be a different * one. @@ -69,13 +69,13 @@ import type { Operation, Result } from "effection"; import { field, object, cli } from "configliere"; import { z } from "zod"; import type { DurableEvent, DurableStream, Json } from "@executablemd/durable-streams"; -import { retainedSource, validateProps } from "@executablemd/core"; +import { savedSource, validateProps } from "@executablemd/core"; import { gitDirectorySelection } from "@executablemd/git"; import type { PropsSchema } from "@executablemd/core"; import type { RootDocumentSource } from "@executablemd/core"; import { definitionComponents, - retainedWorkflowInstallation, + savedWorkflowInstallation, workflowBundleInstallation, WORKFLOW_RUN_STATUSES, WorkflowLifecycle, @@ -97,8 +97,8 @@ import { SUSPENSION_REQUEST } from "@executablemd/workflow"; import { describeError } from "./props.ts"; import { offering, useNoGeneratedEvaluation } from "./workflow-generated.ts"; import { preflightFork } from "./workflow-fork.ts"; -import type { EstablishedDefinition, RetainedSources } from "./workflow-definition.ts"; -import type { RetainedDefinitionSources } from "@executablemd/workflow/deno"; +import type { EstablishedDefinition, SavedSources } from "./workflow-definition.ts"; +import type { SavedDefinitionSources } from "@executablemd/workflow/deno"; import type { WorkflowBundleComponent } from "@executablemd/core/host"; import { decodeSourceText } from "@executablemd/workflow"; import type { WorkflowRun, WorkflowRunRecord } from "@executablemd/workflow"; @@ -106,7 +106,7 @@ import type { WorkflowRun, WorkflowRunRecord } from "@executablemd/workflow"; /** * What this module cannot do without knowing the host. * - * Three operations, all of which reach a run's retained state. Everything else + * Three operations, all of which reach a run's saved state. Everything else * about the lifecycle — the grammar, the definition, the props, the statuses, * the exit codes — is the same wherever a run lives. */ @@ -177,7 +177,7 @@ const EXIT_BY_STATUS: Readonly> = Object.freez running: 1, }); -/** A failure the host classified, rather than an exception message it retained. */ +/** A failure the host classified, rather than an exception message it saved. */ const HOST_FAILURE_CODE = "document-execution-failed"; const HOST_INTERRUPTED_CODE = "executor-interrupted"; const HOST_ORPHANED_CODE = "executor-disappeared"; @@ -212,7 +212,7 @@ const OPTIONS_BY_ACTION: Readonly> = Object.fr start: [...EXECUTION_OPTIONS, "--id"], resume: EXECUTION_OPTIONS, fork: [...EXECUTION_OPTIONS, "--id", "--at"], - // A delivery writes retained state, so it crosses the same gate a durable + // A delivery writes saved state, so it crosses the same gate a durable // event crosses. It runs no document, so nothing else about executing one // applies to it. answer: ["--secret-detection", "--no-secret-detection"], @@ -248,7 +248,7 @@ export const workflowConfig = object({ ...field(z.string().optional()), }, at: { - description: "the retained event a fork continues from (fork only)", + description: "the saved event a fork continues from (fork only)", ...field(z.string().optional()), }, verbose: { @@ -280,13 +280,13 @@ export const workflowConfig = object({ }, artifact: { description: - "inspect this sealed .xmd artifact instead of a retained run (status and history only)", + "inspect this sealed .xmd artifact instead of a saved run (status and history only)", ...field(z.string().optional()), }, output: { description: "the .xmd file to write the artifact to (export only); the artifact contains this " + - "run's complete retained Workspace, which may include source, generated files and " + + "run's complete saved Workspace, which may include source, generated files and " + "secrets — treat it as confidential", ...field(z.string().optional()), }, @@ -301,7 +301,7 @@ export interface WorkflowRequest { readonly verbose: boolean; readonly raw: boolean; readonly secretDetection: boolean; - /** The retained event a fork continues from. Present exactly for `fork`. */ + /** The saved event a fork continues from. Present exactly for `fork`. */ readonly at?: string; /** The definition a fork runs. Present exactly for `fork`. */ readonly definition?: string; @@ -367,7 +367,7 @@ export interface WorkflowExecution { * What the trusted host attaches to this run's one execution. * * An `ExecutionInstallation` rather than something installed into a scope: - * its retained-run admission is applied inside canonical core's own journal + * its saved-run admission is applied inside canonical core's own journal * read, before any middleware or document code exists, and its `prepare` * hook records the run inside the durable root before the root import. */ @@ -416,7 +416,7 @@ function reportRun(runId: string): void { * identifier: `xmd workflow answer` takes the run *and* the suspension, and * without this line the only way to learn the second one is to read the * journal. Reported after the settlement commits, for the same reason the - * status line is — a wait nothing retained is not one to answer. + * status line is — a wait nothing saved is not one to answer. */ function reportSuspension(suspensionId: string): void { report(`workflow suspension: ${suspensionId}`); @@ -435,7 +435,7 @@ function* isCompleted(stream: DurableStream): Operation { /** * The stop reason a failure gets. * - * A retained event that already crossed the secret filter is preferable to a + * A saved event that already crossed the secret filter is preferable to a * code, because it says which effect failed. Anything else becomes one * categorical host code: the alternative is retaining an exception message * beside the journal that filtered it, which is history nothing has filtered. @@ -698,7 +698,7 @@ function inspectionSource( return Err( new Error( `xmd workflow ${action} needs something to inspect — a run id ` + - `(\`xmd workflow ${action} \`) for a retained run, or ` + + `(\`xmd workflow ${action} \`) for a saved run, or ` + `\`xmd workflow ${action} --artifact=\` for a sealed artifact.`, ), ); @@ -885,7 +885,7 @@ export interface WorkflowStart { * status. * * `execute` is the shared CLI's own document machinery, handed everything this - * run decided: the retained source, the retained props, the run's journal, the + * run decided: the saved source, the saved props, the run's journal, the * installations that belong inside the execution scope, and the attachment that * wraps it. * @@ -907,7 +907,7 @@ export function runWorkflow( // executor lock — and a generated one is this invocation's own run. const runId = request.action === "resume" ? request.target : (request.id ?? generatedRunId()); - // A fork begins from the properties the source retained, so the merged set + // A fork begins from the properties the source saved, so the merged set // is decided before the creation that carries it — and therefore before // preflight replays under it, before it is admitted, and before it becomes // the identity a reused fork id is compared against. @@ -940,7 +940,7 @@ export function runWorkflow( return { exitCode: 1 }; } - // Acquired before storage is created or opened, before a retained + // Acquired before storage is created or opened, before a saved // definition is fetched, before replay history is read, before a Workspace // is attached and before the root is imported. Nothing below this line // happens for a run somebody else is running. @@ -1000,12 +1000,12 @@ export function runWorkflow( if (phase.state !== "running") { return; } - const retained = yield* transitions.settle(executorLock, { + const saved = yield* transitions.settle(executorLock, { executionId: execution.executionId, status: "interrupted", reason: { kind: "host", code: HOST_INTERRUPTED_CODE }, }); - if (!retained.ok) { + if (!saved.ok) { // Never claim a status storage refused. What went wrong is reported, // and no `workflow status:` line is published. // @@ -1015,7 +1015,7 @@ export function runWorkflow( // interrupted run exits 130 whether or not its last write landed. What // this run owes the caller is an accurate account, not a different exit // code. - report(retained.error.message); + report(saved.error.message); return; } reportStatus("interrupted"); @@ -1051,7 +1051,7 @@ export function runWorkflow( // The exact target the run retains, never a selector re-resolved now: a // resumed run continues the section it started, and nothing asks the // document layer that question a second time. - root: retainedSource(rootDocumentName(record), source.value.source, { + root: savedSource(rootDocumentName(record), source.value.source, { ...(record.definition.targetPath === undefined ? {} : { target: record.definition.targetPath }), @@ -1064,17 +1064,17 @@ export function runWorkflow( // beside it, through the same host-service slot `xmd run` fills with a // real adapter. installations: [ - retainedWorkflowInstallation(installedRun(record)), + savedWorkflowInstallation(installedRun(record)), // The bundle this run is a run of, when it is a run of one. Both start // and resume install it, and a completed replay installs it too: the - // retained history is held to the same components before its recorded + // saved history is held to the same components before its recorded // output is accepted. ...(source.value.components.length === 0 ? [] : [workflowBundleInstallation(source.value.components)]), ], around(operation: Operation): Operation { - // A completed run replays its retained output and result. Attaching a + // A completed run replays its saved output and result. Attaching a // Workspace for it would open a transaction and capture a root for // work that is not going to happen. A replayed terminal run keeps its // outcome either way — the begin transition preserved it. @@ -1085,7 +1085,7 @@ export function runWorkflow( // // The generated selection is offered inside the attachment, in the // scope that executes the document: a completed replay restores its - // retained output and expands nothing, so it is offered nothing — and + // saved output and expands nothing, so it is offered nothing — and // states that, because this command's ordinary default encloses it and // a run offered nothing is not a run that inherits a vocabulary. // ``'s spelling comes from the Git Plugin, which decides that a @@ -1128,7 +1128,7 @@ export function runWorkflow( if (settlement.kind === "suspension") { // The executor lock is still held, and stays held until this invocation // returns: teardown finished above, and only then may the run claim a - // status. The stop reason names the retained request event rather than + // status. The stop reason names the saved request event rather than // repeating anything the request said. const requested = yield* suspensionEvent(database, settlement.notice.suspensionId); const suspended = yield* transitions.settle(executorLock, { @@ -1150,7 +1150,7 @@ export function runWorkflow( const result = settlement.result; const status: WorkflowRunStatus = result.ok ? "completed" : "failed"; const reason = result.ok ? undefined : yield* failureReason(database); - const retained = yield* transitions.settle(executorLock, { + const saved = yield* transitions.settle(executorLock, { executionId: execution.executionId, status, ...(reason === undefined ? {} : { reason }), @@ -1161,10 +1161,10 @@ export function runWorkflow( if (!result.ok) { report(result.error.message); } - if (!retained.ok) { - // The status was not retained, so it is not published and its exit code + if (!saved.ok) { + // The status was not saved, so it is not published and its exit code // is not this invocation's. Storage refusing is its own failure. - report(retained.error.message); + report(saved.error.message); return { exitCode: 1 }; } phase.state = "settled"; @@ -1302,11 +1302,11 @@ function* startCreation( * The properties this run is created with. * * A `start` supplies its own and nothing else. A fork begins from what the - * source retained: a corrected definition is still a run of the same procedure, + * source saved: a corrected definition is still a run of the same procedure, * and restating every property the original was started with would make * forking a transcription exercise. Whatever the fork command wrote itself * takes precedence, property by property — that is what "add or override" - * means — and a property the source retained under a name the fork also wrote + * means — and a property the source saved under a name the fork also wrote * is the fork's. * * The result is held to the *candidate* document, because that is the document @@ -1340,12 +1340,12 @@ function* forkProps( } /** - * What the source run retained, for a fork to begin from. + * What the source run saved, for a fork to begin from. * * Read through the ordinary read-only inspection surface, before the fork's * executor lock and before anything of the fork exists. Every other action * answers with nothing: a `start` has no source, and a `resume` already has the - * properties its own run retained. + * properties its own run saved. */ function* inheritedProps( request: WorkflowRequest, @@ -1369,11 +1369,11 @@ function* inheritedProps( * second read here would be a second source of truth about what the run is a * run of, and the two could differ. */ -function executableSources(sources: RetainedDefinitionSources): Result { +function executableSources(sources: SavedDefinitionSources): Result { const byPath = new Map(sources.sources.map((source) => [source.path, source.bytes])); const entry = byPath.get(sources.definition.entrypoint); if (entry === undefined) { - return Err(new Error("this run's retained source holds no entrypoint")); + return Err(new Error("this run's saved source holds no entrypoint")); } const source = decodeSourceText(entry); if (!source.ok) { @@ -1383,9 +1383,9 @@ function executableSources(sources: RetainedDefinitionSources): Result each.path === declared.path); - if (bytes === undefined || retained === undefined) { - return Err(new Error(`this run's retained source holds no ${declared.name}`)); + const saved = sources.definition.sources.find((each) => each.path === declared.path); + if (bytes === undefined || saved === undefined) { + return Err(new Error(`this run's saved source holds no ${declared.name}`)); } const content = decodeSourceText(bytes); if (!content.ok) { @@ -1394,7 +1394,7 @@ function executableSources(sources: RetainedDefinitionSources): Result( files: Record, @@ -59,7 +59,7 @@ describe("Tier FE — the ordinary run profile", () => { function* (dir) { const result = yield* runCli(["run", join(dir, "doc.md")], { cwd: dir }).join(); expect(result.code).toBe(0); - expect(result.stdout).toContain("the retained note"); + expect(result.stdout).toContain("the saved note"); }, ); }); diff --git a/packages/cli/tests/evaluate-workflow.test.ts b/packages/cli/tests/evaluate-workflow.test.ts index 6f5477341..38c350ce0 100644 --- a/packages/cli/tests/evaluate-workflow.test.ts +++ b/packages/cli/tests/evaluate-workflow.test.ts @@ -75,7 +75,7 @@ function runDocument( }); } -const NOTE = "the retained note"; +const NOTE = "the saved note"; describe("Tier FE — the workflow host's profile", () => { it("FE22: `` is the host's choice, and Git is what supplies it", function* () { diff --git a/packages/cli/tests/fetch-cli.test.ts b/packages/cli/tests/fetch-cli.test.ts index bc52eb8ec..9086ce1ee 100644 --- a/packages/cli/tests/fetch-cli.test.ts +++ b/packages/cli/tests/fetch-cli.test.ts @@ -158,14 +158,14 @@ describe("Tier FT — diagnostic retention of a Fetch", () => { expect(event?.result?.status).toBe("ok"); const value = event?.result?.value; if (value === null || typeof value !== "object") { - throw new Error("the retained Fetch result is not an object"); + throw new Error("the saved Fetch result is not an object"); } - const retained: Record = { ...value }; - expect(retained.status).toBe(200); - expect(retained.body).toBe('{"greeting":"hello"}'); - const headers = retained.headers; + const saved: Record = { ...value }; + expect(saved.status).toBe(200); + expect(saved.body).toBe('{"greeting":"hello"}'); + const headers = saved.headers; if (headers === null || typeof headers !== "object") { - throw new Error("the retained Fetch response carries no header object"); + throw new Error("the saved Fetch response carries no header object"); } const named: Record = { ...headers }; expect(named["content-type"]).toBe("application/json"); diff --git a/packages/cli/tests/fixtures/plugins/README.md b/packages/cli/tests/fixtures/plugins/README.md index 33cb55a98..94f9e94ab 100644 --- a/packages/cli/tests/fixtures/plugins/README.md +++ b/packages/cli/tests/fixtures/plugins/README.md @@ -21,6 +21,6 @@ it addresses a different context: the document it would have replaced comes out unwrapped. `structural.mjs` declares structural syntax and no Markdown component at all. It -is what proves the retained catalog carries both arms: its construct has to be +is what proves the saved catalog carries both arms: its construct has to be describable by `xmd syntax` and acceptable to Plan validation, not only expandable by a run. diff --git a/packages/cli/tests/fixtures/plugins/structural.mjs b/packages/cli/tests/fixtures/plugins/structural.mjs index 7b0ed9563..d061c185f 100644 --- a/packages/cli/tests/fixtures/plugins/structural.mjs +++ b/packages/cli/tests/fixtures/plugins/structural.mjs @@ -1,7 +1,7 @@ // A Plugin that declares structural syntax and nothing else — no Markdown // component anywhere in it. // -// It exists because the retained catalog has two arms and only one of them used +// It exists because the saved catalog has two arms and only one of them used // to reach `xmd syntax` and ``'s validation. A construct a run expands but // a check has never heard of is the defect: a Plan would be refused for writing // syntax the run supplies. diff --git a/packages/cli/tests/fixtures/repl/issue-881/controlled-host.ts b/packages/cli/tests/fixtures/repl/issue-881/controlled-host.ts index 9be9a0719..605ab6eed 100644 --- a/packages/cli/tests/fixtures/repl/issue-881/controlled-host.ts +++ b/packages/cli/tests/fixtures/repl/issue-881/controlled-host.ts @@ -57,7 +57,7 @@ const LATCH = latches(); * * The `ensure` is registered by `install`, so it runs when this entry's * installation scope is released — which is after the entry's outcome has been - * retained and before the entry's work is finished. Writing the latch is how + * saved and before the entry's work is finished. Writing the latch is how * the harness knows the window is open; waiting for the other one is what * keeps it open. */ diff --git a/packages/cli/tests/fixtures/repl/issue-881/history-crowd.md b/packages/cli/tests/fixtures/repl/issue-881/history-crowd.md index 5ffdbeb0d..4c1ab9765 100644 --- a/packages/cli/tests/fixtures/repl/issue-881/history-crowd.md +++ b/packages/cli/tests/fixtures/repl/issue-881/history-crowd.md @@ -2,7 +2,7 @@ The crowded recorded order (#881 PR 3). Two hundred consecutive invocations of one component. Each invocation admits - a scope, and each admitted scope is one retained position — which is what + a scope, and each admitted scope is one saved position — which is what makes this fixture crowded: two hundred positions is more than the rail has columns at 160, 120 or 72, so marks have to share columns at every measured width. diff --git a/packages/cli/tests/fixtures/repl/reference-style.ts b/packages/cli/tests/fixtures/repl/reference-style.ts index ad8f2c3cc..28f574f84 100644 --- a/packages/cli/tests/fixtures/repl/reference-style.ts +++ b/packages/cli/tests/fixtures/repl/reference-style.ts @@ -144,10 +144,10 @@ export const RAIL = Object.freeze({ * The archive marks a selected row and a focused one with a badge, a variable * font size and an animation, none of which a terminal has. The accepted * presentation retains this product's own `*` cue and row surface instead, and - * names the value: these are the retained terminal adaptations, stated here so + * names the value: these are the saved terminal adaptations, stated here so * a regression asserting one is not reading it back off the product either. */ -export const RETAINED = Object.freeze({ +export const SAVED = Object.freeze({ /** Behind a selected row, for the whole width the frame measured it at. */ selectedSurface: 0x122026, }); diff --git a/packages/cli/tests/fixtures/repl/reference.ts b/packages/cli/tests/fixtures/repl/reference.ts index fb4d6575e..9ae3a3302 100644 --- a/packages/cli/tests/fixtures/repl/reference.ts +++ b/packages/cli/tests/fixtures/repl/reference.ts @@ -117,7 +117,7 @@ export function* referenceEvents(): Operation { * One document holding one of each thing the Agent projection has to read: two * different agents talking in one conversation, one agent talking in two, a * turn that was granted permission while it ran, and a turn refused before it - * reached a provider at all — which is the only way a retained turn has no + * reached a provider at all — which is the only way a saved turn has no * conversation to belong to. * * The provider is scripted rather than recorded, for the reason the entry above @@ -157,7 +157,7 @@ const BUILD_OPTIONS: readonly PermissionOption[] = [ * A provider that answers by the text it was sent. * * `refuse:` refuses before the turn starts, which is what leaves a record with - * no session key. The build turn asks for permission, so one retained turn + * no session key. The build turn asks for permission, so one saved turn * carries an audit and the others carry none. */ function scriptedAgent(): AgentProviderFactory { diff --git a/packages/cli/tests/inline-cli.test.ts b/packages/cli/tests/inline-cli.test.ts index af3cd779c..8ace2084c 100644 --- a/packages/cli/tests/inline-cli.test.ts +++ b/packages/cli/tests/inline-cli.test.ts @@ -233,7 +233,7 @@ describe( }); it("IE18: running an inline document leaves the directory as it was", function* () { - // The distinct fault this retained row catches: inline execution must + // The distinct fault this saved row catches: inline execution must // not add any top-level filesystem entry — a file, a directory, or a // symlink. readdir() observes every entry kind; the Markdown suite's // observes only regular files, so this row stays a focused diff --git a/packages/cli/tests/plan-args.test.ts b/packages/cli/tests/plan-args.test.ts index 152696c68..7baed66b0 100644 --- a/packages/cli/tests/plan-args.test.ts +++ b/packages/cli/tests/plan-args.test.ts @@ -27,7 +27,7 @@ import { const REQUEST = "ask me for my age and write the result to a file"; /** Every option the command still defines, with a value where it takes one. */ -const RETAINED: readonly string[][] = [ +const SAVED: readonly string[][] = [ ["--include", "lib"], ["--agent-provider", "acpx"], ["--default-agent", "codex"], @@ -130,8 +130,8 @@ describe("Tier PR — xmd plan fixed grammar", () => { expect(separated.fixed).toEqual(["plan"]); }); - it("PS1: every retained option is accepted, before and after the request", function* () { - for (const option of RETAINED) { + it("PS1: every saved option is accepted, before and after the request", function* () { + for (const option of SAVED) { expect(scanPlanArgs(["plan", REQUEST, ...option]).error).toBe(undefined); expect(scanPlanArgs(["plan", ...option, REQUEST]).error).toBe(undefined); } @@ -139,10 +139,10 @@ describe("Tier PR — xmd plan fixed grammar", () => { // All of them at once, and every token reaches the parser in the order it // was written: the invocation keeps its own options rather than losing one // to a neighbour. - const scan = scanPlanArgs(["plan", REQUEST, ...RETAINED.flat()]); + const scan = scanPlanArgs(["plan", REQUEST, ...SAVED.flat()]); expect(scan.error).toBe(undefined); expect(scan.request).toBe(REQUEST); - expect(scan.fixed).toEqual(["plan", REQUEST, ...RETAINED.flat()]); + expect(scan.fixed).toEqual(["plan", REQUEST, ...SAVED.flat()]); // `--session` still needs a name. An option the parser reads as absent // falls back to the generated session, so a caller who asked for a named @@ -169,7 +169,7 @@ describe("Tier PR — xmd plan fixed grammar", () => { ["plan", REQUEST, "--run", "--run"], ["plan", "--run", REQUEST], ["plan", "--run=false", REQUEST], - // Written where a retained option would otherwise swallow it: a value + // Written where a saved option would otherwise swallow it: a value // nobody names a directory is how a removed spelling survives. ["plan", REQUEST, "--include", "--run"], ["plan", REQUEST, "--output", "--run"], @@ -273,7 +273,7 @@ describe("Tier PR — xmd plan fixed grammar", () => { }); it("PO7: an option that reaches this grammar after --journal is that option, not a path", function* () { - // The reported hole. Every retained spelling that reaches this grammar, + // The reported hole. Every saved spelling that reaches this grammar, // written where the path goes: reading one as a filename would exclusively // create a file called `--verbose` and drop what the caller actually asked // for, and the scan used to accept all of it in silence. @@ -350,9 +350,9 @@ describe("Tier PR — xmd plan fixed grammar", () => { } // A command line naming none of them has nothing to say here, whatever else - // it holds — including the retained options and `--help` itself. + // it holds — including the saved options and `--help` itself. expect(removedPlanOption(["plan", "--help"])).toBe(undefined); - expect(removedPlanOption(["plan", REQUEST, ...RETAINED.flat()])).toBe(undefined); + expect(removedPlanOption(["plan", REQUEST, ...SAVED.flat()])).toBe(undefined); // A token after the separator is the request, not an option. expect(removedPlanOption(["plan", "--", "--run"])).toBe(undefined); }); diff --git a/packages/cli/tests/plan-cli.test.ts b/packages/cli/tests/plan-cli.test.ts index 705baa31d..98f32f0cf 100644 --- a/packages/cli/tests/plan-cli.test.ts +++ b/packages/cli/tests/plan-cli.test.ts @@ -344,9 +344,9 @@ describe( "Tier PR — the xmd plan command lifecycle", { sanitizeOps: false, sanitizeResources: false }, () => { - it("PS4: plan help states the retained grammar and both compositions", function* () { + it("PS4: plan help states the saved grammar and both compositions", function* () { yield* useWorkingDirectory(function* (dir) { - // Every retained option beside `--help`: help is still help, and none + // Every saved option beside `--help`: help is still help, and none // of them turns it into a refusal. const { code, stdout, stderr } = yield* runCli( [ @@ -881,7 +881,7 @@ describe( materializations.push(harness.fake.ensured[0]?.materialization); // Neither invocation ran the program it produced: the file that // program writes is nowhere, and the failure it ends in never - // happened. The retained Plan artifact is not an execution record. + // happened. The saved Plan artifact is not an execution record. expect(`${invocation}: ${(yield* until(readdir(dir))).join()}`).toBe(`${invocation}: `); }); } @@ -1138,13 +1138,13 @@ describe( yield* refusedEarly([REQUEST, "--trace"], "unrecognized option for xmd plan: --trace"); }); - it("PO7: a retained option written where the journal path goes refuses before any phase", function* () { + it("PO7: a saved option written where the journal path goes refuses before any phase", function* () { // `refusedEarly` carries an agent name nothing resolves and an isolated // `HOME`, so reaching the catalog, the provider or a session placement // would each leave a trace. None of them does — and neither does the // filesystem: the switch that was about to become a filename is not one. // - // Every retained spelling that reaches this command's own grammar. Help + // Every saved spelling that reaches this command's own grammar. Help // is not among them and is the case after next: it is lifted out of the // command line before any command's grammar runs. for (const swallowed of [ @@ -1300,7 +1300,7 @@ describe( expect(events.length).toBeGreaterThan(answered.entries); expect(events.at(-1)?.type).toBe("close"); // It records authorship and no later program run. The approved source - // is *in* the file, because a draft is retained content — what is not + // is *in* the file, because a draft is saved content — what is not // there is any effect of running it: no execution event, and none of // the file the program writes. expect(yield* until(readdir(dir))).toEqual(["authorship.jsonl"]); diff --git a/packages/cli/tests/plan-command-document.test.ts b/packages/cli/tests/plan-command-document.test.ts index 69623d7bd..d41b3b6cc 100644 --- a/packages/cli/tests/plan-command-document.test.ts +++ b/packages/cli/tests/plan-command-document.test.ts @@ -31,7 +31,7 @@ import { join } from "node:path"; import { agentIdentityComponents, installAgentComponents, - retainedSource, + savedSource, useNormalizedOutput, } from "@executablemd/core"; import type { @@ -238,7 +238,7 @@ function* runDocument(options: RunOptions = {}): Operation { } const execution = yield* executeInstalled( { - ...retainedSource(PLAN_COMMAND_IDENTITY, source), + ...savedSource(PLAN_COMMAND_IDENTITY, source), stream: new InMemoryStream(), includes: [], secretDetection: true, @@ -573,7 +573,7 @@ describe("the packaged plan command document", () => { yield* useSyntax(harness.symbols); const execution = yield* executeInstalled( { - ...retainedSource(PLAN_COMMAND_IDENTITY, source), + ...savedSource(PLAN_COMMAND_IDENTITY, source), stream: new InMemoryStream(), includes: [], secretDetection: true, @@ -849,7 +849,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI2, PI12: findings reach the next turn, and default progress says only how many", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ turns: [{ reply: COMPOSED }, { reply: CANDIDATE }], generated: { File: ["self-closing"], Glob: ["self-closing"], Syntax: ["self-closing"] }, @@ -865,7 +865,7 @@ describe("Tier PI — read-only information requests", () => { // three bindings are in it, gathered by one `` the candidate chose // the shape of rather than by anything collecting observations for it. const followUp = run.prompts[1] ?? ""; - expect(followUp).toContain("the retained note"); + expect(followUp).toContain("the saved note"); expect(followUp).toContain("notes.md"); expect(followUp).toContain("Available in this evaluation"); expect(followUp).toContain("They are context for writing the Plan"); @@ -875,12 +875,12 @@ describe("Tier PI — read-only information requests", () => { const progress = run.progress.join(""); expect(progress).toContain("Inspecting XMD information"); expect(progress).toContain("Information request 1 of 8"); - expect(progress).not.toContain("the retained note"); + expect(progress).not.toContain("the saved note"); expect(progress).not.toContain(" { const progress = run.progress.join(""); expect(progress).toContain("XMD information request"); expect(progress).toContain("XMD information returned"); - expect(progress).toContain("the retained note"); + expect(progress).toContain("the saved note"); // The request is shown before its findings, and both after the phase that // announced them. expect(progress.indexOf("XMD information request")).toBeLessThan( @@ -900,7 +900,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI10: a refused request is one safe retry, and names no host detail", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ // A paired `` write under a read-only selection: refused whole, // before any read. @@ -918,7 +918,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI7: successes and refusals share one budget, and the ninth is not evaluated", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n", "ninth.md": "unreached\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n", "ninth.md": "unreached\n" }); // Alternating: a request that succeeds, then one refused whole for writing. // Eight of them, so the budget is spent by two kinds of outcome rather than // by either alone. @@ -952,7 +952,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI7: requests interleave with the initial, repair and revision turns", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const ask = { reply: '\n\n' }; const run = yield* runDocument({ // One request at each of the three turns that can produce a Plan: the @@ -1058,7 +1058,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI2: an empty match renders as an empty array", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ turns: [ { reply: '\n\n' }, @@ -1076,7 +1076,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI4: a prohibited operation anywhere in the request refuses before any read", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ // The admitted read is written first, and the prohibited write sits in // the branch the condition never takes. A refusal that happened @@ -1095,12 +1095,12 @@ describe("Tier PI — read-only information requests", () => { expect(run.failure).toBe(undefined); expect(files.performed).toEqual([]); - expect(files.entries.get("notes.md")).toBe("the retained note\n"); + expect(files.entries.get("notes.md")).toBe("the saved note\n"); expect(run.prompts[1] ?? "").toContain("That request was refused"); }); it("PI6, PI12: nothing is disclosed or asked until the request has settled", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ turns: [{ reply: COMPOSED }, { reply: CANDIDATE }], generated: { File: ["self-closing"], Glob: ["self-closing"], Syntax: ["self-closing"] }, @@ -1221,7 +1221,7 @@ describe("Tier PI — read-only information requests", () => { } it("PI6: findings and the following turn wait for cleanup to finish", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const held = barrier(); /** What the observer saw while cleanup was still held. */ const whileHeld: { progress: string; prompts: number; performed: string[] } = { @@ -1272,7 +1272,7 @@ describe("Tier PI — read-only information requests", () => { // Released: both happened, and the next turn carries the findings. expect(run.progress.join("")).toContain("XMD information returned"); expect(run.prompts).toHaveLength(2); - expect(run.prompts[1] ?? "").toContain("the retained note"); + expect(run.prompts[1] ?? "").toContain("the saved note"); expect(run.reviews).toHaveLength(1); }); @@ -1299,7 +1299,7 @@ describe("Tier PI — read-only information requests", () => { * already-classified refusal and stays terminal. */ it("PI6: a recoverable refusal is disclosed, then the following turn begins", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ turns: [{ reply: '\n' }, { reply: CANDIDATE }], generated: { File: ["self-closing"], Glob: ["self-closing"], Syntax: ["self-closing"] }, @@ -1319,7 +1319,7 @@ describe("Tier PI — read-only information requests", () => { }); it("PI3: a titled draft is never evaluated", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ turns: [{ reply: CANDIDATE }], generated: { File: ["self-closing"], Glob: ["self-closing"], Syntax: ["self-closing"] }, @@ -1363,7 +1363,7 @@ describe("Tier PI — the ordinary selection offers the question with the write" }); it("EL3: a read-only information request that asks a question is refused whole", function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const asked: string[] = []; const run = yield* scoped(function* () { yield* Elicitation.around( diff --git a/packages/cli/tests/plan-component.test.ts b/packages/cli/tests/plan-component.test.ts index da629c0c2..9389c108a 100644 --- a/packages/cli/tests/plan-component.test.ts +++ b/packages/cli/tests/plan-component.test.ts @@ -27,7 +27,7 @@ import { agentIdentityComponents, collect, installAgentComponents, - retainedSource, + savedSource, useNormalizedOutput, } from "@executablemd/core"; import type { Json, SyntaxSymbols } from "@executablemd/core"; @@ -198,7 +198,7 @@ function* runDocument(options: { } const execution = yield* executeInstalled( { - ...retainedSource(ROOT, options.source), + ...savedSource(ROOT, options.source), stream, includes: [...(options.includes ?? [])], ...(options.props === undefined ? {} : { props: options.props }), @@ -320,7 +320,7 @@ describe("Tier PC — in an ordinary document", () => { }); expect(run.failure).toBe(undefined); - // The retained catalog is what the first turn was built from, and it is + // The saved catalog is what the first turn was built from, and it is // there exactly once — a second copy would mean the Component both bound // it and emitted it. const first = run.harness.fake.prompts[0] ?? ""; @@ -531,7 +531,7 @@ describe("Tier PC — in an ordinary document", () => { it("PI4: an embedded Plan reads through the document host's own profile", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ source: SOURCE, harness: yield* asking(yield* planWriterRoot()), @@ -618,7 +618,7 @@ describe("Tier PC — in an ordinary document", () => { */ it("PI21: a read restriction denies the request and becomes one safe retry", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ source: SOURCE, harness: yield* asking(yield* planWriterRoot()), @@ -739,7 +739,7 @@ describe("Tier PC — in an ordinary document", () => { */ it("PI26: a throwing Files provider ends authorship with no retry turn", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ source: SOURCE, harness: yield* reading_(yield* planWriterRoot()), @@ -780,7 +780,7 @@ describe("Tier PC — in an ordinary document", () => { let joined = false; const files = recordedFiles( - { "notes.md": "the retained note\n" }, + { "notes.md": "the saved note\n" }, { *hold(path) { if (path !== "notes.md") { @@ -850,7 +850,7 @@ describe("Tier PC — in an ordinary document", () => { */ it("PI28: a Plan outside the narrower region uses the restored allowance", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const harness = yield* planDeclarationHarness({ surface: "component", planWriterRoot: yield* planWriterRoot(), @@ -904,13 +904,13 @@ describe("Tier PC — in an ordinary document", () => { // Inside the region: refused, and the refusal is what the next turn got. expect(prompts[1] ?? "").toContain("That request was refused"); // Outside it: performed, and the findings are what the next turn got. - expect(prompts[3] ?? "").toContain("the retained note"); + expect(prompts[3] ?? "").toContain("the saved note"); }); }); it("PI12: an embedded request emits no command progress into the document", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const run = yield* runDocument({ source: SOURCE, harness: yield* asking(yield* planWriterRoot()), @@ -945,7 +945,7 @@ describe("Tier PC — in an ordinary document", () => { it("PI4: a prohibited operation in the request refuses with no effect", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const harness = yield* planDeclarationHarness({ surface: "component", planWriterRoot: yield* planWriterRoot(), @@ -970,14 +970,14 @@ describe("Tier PC — in an ordinary document", () => { expect(run.failure).toBe(undefined); expect(files.performed).toEqual([]); - expect(files.entries.get("notes.md")).toBe("the retained note\n"); + expect(files.entries.get("notes.md")).toBe("the saved note\n"); }); }); it("PI8: a completed request replays with every live reader tripped", function* () { yield* useWorkingDirectory(function* () { const first = new InMemoryStream(); - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const one = yield* runDocument({ source: SOURCE, harness: yield* asking(yield* planWriterRoot()), @@ -992,7 +992,7 @@ describe("Tier PC — in an ordinary document", () => { // The complete history, close included. Every live reader is a // tripwire: a turn asked again shows in the prompts, a review in the // reviews, and a search in this recorder. - const tripwire = recordedFiles({ "notes.md": "the retained note\n" }); + const tripwire = recordedFiles({ "notes.md": "the saved note\n" }); const two = yield* runDocument({ source: SOURCE, reviews: [], @@ -1012,7 +1012,7 @@ describe("Tier PC — in an ordinary document", () => { it("PI8: a partial continuation resumes at the first unrecorded effect", function* () { yield* useWorkingDirectory(function* () { const first = new InMemoryStream(); - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const one = yield* runDocument({ source: SOURCE, harness: yield* asking(yield* planWriterRoot()), @@ -1027,7 +1027,7 @@ describe("Tier PC — in an ordinary document", () => { // reaches the request again. The Agent turns and the review are durable // and restore; `` records no durable effect of its own, so it // traverses again — the accepted rule rather than repeated history. - const resumed = recordedFiles({ "notes.md": "the retained note\n" }); + const resumed = recordedFiles({ "notes.md": "the saved note\n" }); const two = yield* runDocument({ source: SOURCE, reviews: [], @@ -1044,15 +1044,15 @@ describe("Tier PC — in an ordinary document", () => { }); }); - it("PI8: a retained selection refusal restores without asking the provider again", function* () { + it("PI8: a saved selection refusal restores without asking the provider again", function* () { yield* useWorkingDirectory(function* () { // The defect this replaces: the refusal was noticed in a closure inside // the durable executor. On replay the executor is skipped, so the - // closure was unset and the retained refusal came back carrying no + // closure was unset and the saved refusal came back carrying no // classification — the same request was recoverable live and terminal // after a partial continuation. const first = new InMemoryStream(); - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const harness = yield* planDeclarationHarness({ surface: "component", planWriterRoot: yield* planWriterRoot(), @@ -1084,14 +1084,14 @@ describe("Tier PC — in an ordinary document", () => { source: SOURCE, reviews: [], generated: reading(), - files: recordedFiles({ "notes.md": "the retained note\n" }), + files: recordedFiles({ "notes.md": "the saved note\n" }), stream: yield* continuing(first), harness: yield* planDeclarationHarness({ surface: "component", planWriterRoot: yield* planWriterRoot(), *symbols() { catalogs += 1; - throw new Error("a retained syntax selection was asked live"); + throw new Error("a saved syntax selection was asked live"); }, }), }); @@ -1110,7 +1110,7 @@ describe("Tier PC — in an ordinary document", () => { yield* useWorkingDirectory(function* () { // The instruction arrives through props, which is what can actually // differ on a continuation: PC23 settles that a changed authored body - // is never expanded, because the retained root replays instead. + // is never expanded, because the saved root replays instead. const source = [ "---", "props:", @@ -1128,7 +1128,7 @@ describe("Tier PC — in an ordinary document", () => { const root = yield* planWriterRoot(); const first = new InMemoryStream(); - const files = recordedFiles({ "notes.md": "the retained note\n" }); + const files = recordedFiles({ "notes.md": "the saved note\n" }); const one = yield* runDocument({ source, props: { request: "Write a program." }, @@ -1143,9 +1143,9 @@ describe("Tier PC — in an ordinary document", () => { expect(files.performed).toEqual(["glob notes.md"]); // A partial history, so expansion re-enters the Component rather than - // replaying its retained root whole — which is where the frozen inputs - // compare the instruction the retained authorship was made for. - const changed = recordedFiles({ "notes.md": "the retained note\n" }); + // replaying its saved root whole — which is where the frozen inputs + // compare the instruction the saved authorship was made for. + const changed = recordedFiles({ "notes.md": "the saved note\n" }); const two = yield* runDocument({ source, props: { request: "Write something else." }, @@ -1159,7 +1159,7 @@ describe("Tier PC — in an ordinary document", () => { expect(two.failure).toContain("stale input"); expect(two.failure).toContain("none was written for the new instructions"); // Refused in the frozen inputs: before a turn, a review, or a single - // read of the request the retained history answered. + // read of the request the saved history answered. expect(changed.performed).toEqual([]); expect(two.harness.fake.prompts).toEqual([]); expect(two.harness.reviews).toEqual([]); @@ -1221,7 +1221,7 @@ describe("Tier PC — in an ordinary document", () => { } const validation = yield* validateDocument({ - ...retainedSource(ROOT, '\n'), + ...savedSource(ROOT, '\n'), includes: [dir], declarations: [yield* planComponentDescription()], }); @@ -1488,13 +1488,13 @@ describe("Tier PC — in an ordinary document", () => { }); /** - * The Plan artifact a run retained, or nothing when it retained none. + * The Plan artifact a run saved, or nothing when it saved none. * * Read from the journal rather than from the binding, because retention is what * a continuation reads and an ending that produced no Plan must leave nothing * there for one to find. */ - function* retainedArtifact(stream: InMemoryStream): Operation { + function* savedArtifact(stream: InMemoryStream): Operation { for (const event of yield* stream.readAll()) { if (event.type !== "yield" || !event.description.name.startsWith("plan:artifact:")) { continue; @@ -1507,7 +1507,7 @@ describe("Tier PC — in an ordinary document", () => { } /** - * The same history, with one Plan record's retained value replaced. + * The same history, with one Plan record's saved value replaced. * * A continuation reads what the journal holds, and what it holds is not * guaranteed to be what this version wrote — a record can be truncated, edited, @@ -1673,7 +1673,7 @@ describe("Tier PC — in an ordinary document", () => { }); }); - it("PC20: the retained artifact carries the approved bytes and their digest", function* () { + it("PC20: the saved artifact carries the approved bytes and their digest", function* () { yield* useWorkingDirectory(function* () { const stream = new InMemoryStream(); const run = yield* runDocument({ @@ -1685,10 +1685,10 @@ describe("Tier PC — in an ordinary document", () => { }); expect(run.failure).toBe(undefined); - // Retained before either form could publish the bytes: all five members, + // Saved before either form could publish the bytes: all five members, // and exactly those five. A sixth would be something a later reader has // to account for, and a missing one is a record that cannot be read. - const artifact = Object(yield* retainedArtifact(stream)); + const artifact = Object(yield* savedArtifact(stream)); expect(Object.keys(artifact).sort()).toEqual([ "admission", "digest", @@ -1719,8 +1719,8 @@ describe("Tier PC — in an ordinary document", () => { expect(run.failure).toBe("Plan authorship stopped at your request. No Plan was returned."); expect(run.output).not.toContain("got:"); expect(run.emitted).not.toContain("# Say hello"); - // Nothing retained, so nothing a later evaluation could restore. - expect(yield* retainedArtifact(stream)).toBe(undefined); + // Nothing saved, so nothing a later evaluation could restore. + expect(yield* savedArtifact(stream)).toBe(undefined); }); }); @@ -1760,9 +1760,9 @@ describe("Tier PC — in an ordinary document", () => { }); }); - it("PC23: a continuation replays the retained root, so a changed body never runs", function* () { + it("PC23: a continuation replays the saved root, so a changed body never runs", function* () { yield* useWorkingDirectory(function* () { - // The retained-root negative control the story names. A continuation runs + // The saved-root negative control the story names. A continuation runs // the root the journal kept, so a changed authored body is never expanded // and is not the changed-instruction case below. const first = new InMemoryStream(); @@ -1834,7 +1834,7 @@ describe("Tier PC — in an ordinary document", () => { expect(two.failure).toContain("stale input"); expect(two.failure).toContain("none was written for the new instructions"); - // Neither the retained Plan nor a newly authored one. + // Neither the saved Plan nor a newly authored one. expect(two.output).not.toContain("# Say hello"); expect(two.output).not.toContain("got:"); // Refused in the frozen inputs: before a turn, a review, a check or a @@ -1846,7 +1846,7 @@ describe("Tier PC — in an ordinary document", () => { }); }); - it("PC25: a retained inputs record this version cannot read produces nothing", function* () { + it("PC25: a saved inputs record this version cannot read produces nothing", function* () { yield* useWorkingDirectory(function* () { // Three ways a record stops being one: a member gone, a member added, and // a member of the wrong type. Each is refused with the same fixed @@ -1879,13 +1879,13 @@ describe("Tier PC — in an ordinary document", () => { // The record is canonical core's, not Plan's: the packaged Component // writes the same public `` any document writes, so what a // continuation restores is a `syntax_symbols` reference rather than - // anything this host retained. + // anything this host saved. const reference = (yield* approved.readAll()).find( (event) => event.type === "yield" && event.description.type === "syntax_symbols", ); expect(reference?.type).toBe("yield"); if (reference?.type !== "yield" || reference.result.status !== "ok") { - throw new Error("the approved run retained no syntax record"); + throw new Error("the approved run saved no syntax record"); } const value = Object(reference.result.value); expect(Object.keys(value)).toEqual(["symbols"]); @@ -1905,7 +1905,7 @@ describe("Tier PC — in an ordinary document", () => { for (const [, replace] of cases) { const run = yield* continued(yield* tampered(approved, "syntax_symbols:", replace)); - expect(run.failure).toContain("retained text is not a record"); + expect(run.failure).toContain("saved text is not a record"); expect(run.output).not.toContain("got:"); expect(run.output).not.toContain("# Say hello"); expect(run.harness.fake.prompts).toEqual([]); @@ -1914,7 +1914,7 @@ describe("Tier PC — in an ordinary document", () => { }); }); - it("PC26: a retained artifact this version cannot read produces nothing", function* () { + it("PC26: a saved artifact this version cannot read produces nothing", function* () { yield* useWorkingDirectory(function* () { const cases: [string, (value: Json) => Json][] = [ [ @@ -2095,8 +2095,8 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => { it("FE22: the approved bytes are inert text `` then runs", function* () { yield* useWorkingDirectory(function* (dir) { - yield* writeTextFile(join(dir, "notes.md"), "the retained note"); - const files = recordedFiles({ "notes.md": "the retained note" }); + yield* writeTextFile(join(dir, "notes.md"), "the saved note"); + const files = recordedFiles({ "notes.md": "the saved note" }); // What the agent approves is a program. `` hands it back as a // string and performs none of it. const PROGRAM = `# Read the note\n\n\n`; @@ -2122,13 +2122,13 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => { expect(files.performed).toEqual(["read notes.md"]); // And `` did run it, so the row is not passing because nothing // happened at all. - expect(run.output).toContain("the retained note"); + expect(run.output).toContain("the saved note"); }); }); it("FE22: an approved program is not run when no `` is written", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note" }); + const files = recordedFiles({ "notes.md": "the saved note" }); const run = yield* runDocument({ source: ['Write a program.', "", "done", ""].join("\n"), reply: `# Read the note\n\n\n`, @@ -2147,7 +2147,7 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => { it("FE8: a Plan inside `` is told the admitted vocabulary alone", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note" }); + const files = recordedFiles({ "notes.md": "the saved note" }); const seen: string[] = []; const run = yield* scoped(function* () { @@ -2202,7 +2202,7 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => { it("FE8: a fragment naming the wide-only component refuses with no operation", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note" }); + const files = recordedFiles({ "notes.md": "the saved note" }); const seen: string[] = []; const run = yield* scoped(function* () { @@ -2232,7 +2232,7 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => { it("FE9: a deferred Plan keeps its own site's wider vocabulary", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note" }); + const files = recordedFiles({ "notes.md": "the saved note" }); const seen: string[] = []; const run = yield* scoped(function* () { @@ -2282,7 +2282,7 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => { it("FE9: a later narrower Evaluate refuses text the wider site allowed", function* () { yield* useWorkingDirectory(function* () { - const files = recordedFiles({ "notes.md": "the retained note" }); + const files = recordedFiles({ "notes.md": "the saved note" }); // The agent, told the document's own vocabulary, writes something a // fragment may not contain. The narrower `` refuses it before // any effect rather than running part of it. diff --git a/packages/cli/tests/plugin-host.test.ts b/packages/cli/tests/plugin-host.test.ts index 0fa516117..802856dfb 100644 --- a/packages/cli/tests/plugin-host.test.ts +++ b/packages/cli/tests/plugin-host.test.ts @@ -714,7 +714,7 @@ describe("PH8 — an incomplete structural half never reaches a command", () => }); it("stops before xmd syntax can advertise the construct", function* () { - // The defect this closes: the assembly retained a flattened declaration + // The defect this closes: the assembly saved a flattened declaration // list, `inspectSyntax` reads declarations without handlers by design, and // the construct was therefore described to a writer — and refused only when // a run finally tried to expand it. diff --git a/packages/cli/tests/pull-request-read-fork.test.ts b/packages/cli/tests/pull-request-read-fork.test.ts index ee4bcc6e6..48f1f616e 100644 --- a/packages/cli/tests/pull-request-read-fork.test.ts +++ b/packages/cli/tests/pull-request-read-fork.test.ts @@ -1,9 +1,9 @@ /** * Tier PRR — a fork that inherits a completed pull-request read. * - * The question is what a fork does with retained evidence at a durable position + * The question is what a fork does with saved evidence at a durable position * it inherits. Answering it needs a checkpoint *after* the read, not before: a - * fork that begins earlier never presents the retained collection to anything, + * fork that begins earlier never presents the saved collection to anything, * and would prove nothing about either branch. * * Both branches matter, and the control is what keeps the other honest. If the @@ -204,7 +204,7 @@ describe("Tier PRR — forking a run that already read a pull request", () => { expect(changed.code).not.toBe(0); // Refused *as a divergence at this position*, which is the only reason // that means anything here: an argument or usage failure would also be a - // non-zero exit and would prove nothing about the retained read. + // non-zero exit and would prove nothing about the saved read. expect(changed.stderr).toContain("diverges"); expect(changed.stderr).toContain(checkpoint); expect(changed.stdout).not.toContain("reviews: 2"); diff --git a/packages/cli/tests/repl-agent-execution.test.ts b/packages/cli/tests/repl-agent-execution.test.ts index ea45795df..9f3e2d3d5 100644 --- a/packages/cli/tests/repl-agent-execution.test.ts +++ b/packages/cli/tests/repl-agent-execution.test.ts @@ -126,7 +126,7 @@ interface Scripted { * Two requests from one turn, both raised before either is answered. * * Concurrent on purpose: the order they are *answered* in is then the test's - * to choose, which is what proves a retained audit keeps the order they + * to choose, which is what proves a saved audit keeps the order they * arrived in instead. */ readonly permissions?: readonly { @@ -632,7 +632,7 @@ function watchAppends(holder: ReplExecution): (count: number) => Operation } /** - * Every permission this session's retained turns hold, turn by turn. + * Every permission this session's saved turns hold, turn by turn. * * Read from the projected model rather than from the ledger: what a row about a * durable audit is entitled to is what the Journal says, after core parsed it @@ -798,7 +798,7 @@ describe("L2 — exact atomic live-to-durable replacement", () => { expect(session.agent.turns).toHaveLength(0); // Distinct conversations, identical text: nothing correlated by content. // The two conversations the authored `` elements named, each - // carrying its own retained turn although the text is identical. + // carrying its own saved turn although the text is identical. expect(new Set(session.model.turns.map((one) => one.sessionKey))).toEqual( new Set(["stub:planner", "stub:reviewer"]), ); @@ -861,8 +861,8 @@ describe("L2 — exact atomic live-to-durable replacement", () => { expect(outcome.ok).toBe(true); // And the second record replaced only the turn that was left. expect(session.agent.turns).toEqual([]); - // Both conversations retained, each exactly once. Compared as a set: a - // retained turn is ordered by its Prompt sequence, and two concurrent + // Both conversations saved, each exactly once. Compared as a set: a + // saved turn is ordered by its Prompt sequence, and two concurrent // children allocate that sequence in whichever order they reach it — so // source order is what the *rendering* preserves, not what this list does. expect(new Set(session.model.turns.map((one) => one.sessionKey))).toEqual( @@ -897,7 +897,7 @@ describe("L2 — exact atomic live-to-durable replacement", () => { ); yield* replayed.join(); // Restored rather than re-run: the provider was never asked, and the - // retained turn produced no reading of its own. + // saved turn produced no reading of its own. expect(stub.asked).toEqual([]); expect(replayed.agent.turns).toEqual([]); expect(replayed.model.turns).toHaveLength(1); @@ -1283,7 +1283,7 @@ describe("P2 — one live request, one direct settlement", () => { expect(session.agent.requests).toEqual([]); expect(session.model.turns).toHaveLength(1); expect(session.model.turns[0]!.text).toBe("resumed"); - // Retained once, as the denial it was — not as a cancellation, and not twice. + // Saved once, as the denial it was — not as a cancellation, and not twice. expect(audited(session)).toEqual([ { toolCallId: "call-1", @@ -1295,7 +1295,7 @@ describe("P2 — one live request, one direct settlement", () => { ]); }); - it("P2: a cold retained audit has no pending reading and no authority", function* () { + it("P2: a cold saved audit has no pending reading and no authority", function* () { const AUDIT = [ { toolCallId: "call-1", @@ -1349,7 +1349,7 @@ describe("P2 — one live request, one direct settlement", () => { yield* reopened.join(); // The same audit, read from the history rather than observed again. expect(audited(reopened)).toEqual(AUDIT); - // The audit is retained model data; replay recreates no wait from it. + // The audit is saved model data; replay recreates no wait from it. expect(reopened.agent.requests).toEqual([]); expect(reopened.permissions.choose("request-1", "once")).toBe(false); expect(stub.asked).toEqual([]); @@ -1666,7 +1666,7 @@ describe("P5 — the durable audit of a live REPL turn", () => { const session = opened(yield* start(holder, ONE_PROMPT, "approve-reads")); const outcome = yield* session.join(); expect(outcome.ok).toBe(true); - // The decision was made and retained. + // The decision was made and saved. expect(audited(session)).toHaveLength(1); // And what the provider owned stayed the provider's: not in the bytes, not // in the model, not under any name. @@ -1778,7 +1778,7 @@ describe("P6 — only a canonical Prompt claims a record", () => { expect(session.agent.turns).toEqual([]); }); - it("P6: a refused publication leaves the turn neither retained nor mounted", function* () { + it("P6: a refused publication leaves the turn neither saved nor mounted", function* () { const stub = createStub({ one: { deltas: ["reply"] } }); yield* useStub(stub); const holder = refusingExecution(); @@ -1788,7 +1788,7 @@ describe("P6 — only a canonical Prompt claims a record", () => { // The provider really ran and really finished, so the overlay was // terminal at the moment publication was refused. expect(stub.asked).toEqual(["one"]); - // Nothing retained it. + // Nothing saved it. expect(outcome.ok).toBe(false); expect(session.model.turns).toEqual([]); // And nothing is still mounted waiting for a record that will never come. diff --git a/packages/cli/tests/repl-agent-interface.test.ts b/packages/cli/tests/repl-agent-interface.test.ts index dc920a5a8..2343e8f10 100644 --- a/packages/cli/tests/repl-agent-interface.test.ts +++ b/packages/cli/tests/repl-agent-interface.test.ts @@ -965,7 +965,7 @@ function* asking( describe("U1 — one chronology, filtered, undisturbed by the background", () => { beforeAll(() => useTempFileCompiler()); - it("U1: retained, streaming and queued turns show together, in scheduling order", function* () { + it("U1: saved, streaming and queued turns show together, in scheduling order", function* () { const { session, stub } = yield* asking({ review: {}, build: { streaming: true }, @@ -1030,7 +1030,7 @@ describe("U1 — one chronology, filtered, undisturbed by the background", () => expect(yield* labelOf(before, last)).toContain("completed, recorded"); // The earlier turn is still live and the later one is already in the // history, and the earlier one is still first. Appending the live list to - // the retained one would put it last. + // the saved one would put it last. const shown = (yield* turnRows(before)).map((one) => one.label); expect(shown.findIndex((label) => showsPrompt(label, first))).toBeLessThan( shown.findIndex((label) => showsPrompt(label, last)), @@ -1435,6 +1435,7 @@ describe("U2 — one action path, and one owner for a pending request", () => { check: { queued: true }, }); yield* until(session, "a request waiting", () => session.agent.requests.length === 1); + yield* until(session, "both started turns", () => started(session) === 2); const request = session.agent.requests[0]; if (request === undefined) { throw new Error("no request was published"); @@ -1608,7 +1609,7 @@ describe("U2 — one action path, and one owner for a pending request", () => { it("U2: a recorded permission audit is read, never answered", function* () { // Reconstructed from a journal a real run recorded, because what this row owns - // is how a *retained* audit is presented: read, with no target, no action and + // is how a *saved* audit is presented: read, with no target, no action and // no authority call. How a live turn comes to retain one is Slice B's, and is // proved there. const holder: ReplExecution = { @@ -2790,7 +2791,7 @@ describe("U8 — a target is a control, whatever its key is spelled", () => { expect(drawerFrame.node(key)).toBeDefined(); expect(drawerFrame.targetable(key)).toBe(false); } - // A turn's own facts and a retained audit are lines wherever they appear. + // A turn's own facts and a saved audit are lines wherever they appear. const sessions = reading(onSessions(session), session, WIDE); const sessionsFrame = yield* drawn(tree, sessions); for (const key of sessionsFrame.keys) { @@ -3604,7 +3605,7 @@ describe("EU1 — selecting an entry moves the transcript locus and nothing else yield* until(session, "the second entry's turn being recorded", () => recorded(session) === 2); yield* session.join(); - // Read at one retained position, so this round trip carries history state + // Read at one saved position, so this round trip carries history state // alongside the selection, the draft and the conversation filter. The last // one, because what each entry's transcript holds has to be distinguishable // for the locus below to be a claim about which entry is being read. @@ -3993,7 +3994,7 @@ describe("U2 — a draft outlives a permission and is still editable", () => { yield* pressUntil(terminal, "Entries"); terminal.feed("\r"); yield* settled(40); - // Not `[ok]`, which is the retained close: a close is recorded while the + // Not `[ok]`, which is the saved close: a close is recorded while the // task that wrote it is still coming down, and a submission taken in that // window has nowhere to go. The row that says the next entry may start is // the one that means the teardown finished too (#870 UI11). diff --git a/packages/cli/tests/repl-agent-journey.test.ts b/packages/cli/tests/repl-agent-journey.test.ts index e53ae4d63..4278ab5e4 100644 --- a/packages/cli/tests/repl-agent-journey.test.ts +++ b/packages/cli/tests/repl-agent-journey.test.ts @@ -213,13 +213,13 @@ describe("J1 — the Story, from one entry to one README", () => { // Taken, and streaming: the turn is active before it has said anything. held.start(AUTHORSHIP); yield* showing(terminal, "· streaming"); - // Ended, with the fact its provider reported, and then retained: the row a + // Ended, with the fact its provider reported, and then saved: the row a // person was watching becomes its own record, under the scope the packaged // Plan was admitted as. held.deltas(AUTHORSHIP); yield* showing(terminal, "stopped: end_turn"); yield* showing(terminal, "completed, recorded"); - yield* awaiting("the turn was never retained", function* () { + yield* awaiting("the turn was never saved", function* () { return ( recorded(yield* journal(hostRoot)).filter((kind) => kind === "agent_prompt").length > 0 ); @@ -320,7 +320,7 @@ describe("J1 — the Story, from one entry to one README", () => { }); expect(yield* untilResolved(readdir(workspace))).toEqual(["README.md"]); - // Both authorship turns are retained, under the scope the packaged Plan + // Both authorship turns are saved, under the scope the packaged Plan // was admitted as: the entry holds a `Plan` component scope, the program // it produced hangs under it, and both rows are recorded ones rather than // this process's live readings. @@ -331,7 +331,7 @@ describe("J1 — the Story, from one entry to one README", () => { expect(occurrences(terminal, "completed, recorded")).toBe(2); expect(shows(terminal, "component Plan")).toBe(true); expect(shows(terminal, "generated generated")).toBe(true); - // And both Plan reviews are retained where they were asked, which is + // And both Plan reviews are saved where they were asked, which is // inside that scope rather than in the entry. expect( yield* answeredUnder(terminal, "component Plan", "answered @executablemd/cli/Plan"), @@ -623,7 +623,7 @@ describe("J3 — durable truth, and a cold process over it", () => { yield* untilDrawn(second.terminal); // The whole journey, from the Journal: the packaged Plan's own scope, the - // program it produced, both retained turns and their conversation, the + // program it produced, both saved turns and their conversation, the // forms that were answered, the preview the program rendered and the // write it reported. for (const line of [ @@ -638,18 +638,18 @@ describe("J3 — durable truth, and a cold process over it", () => { yield* showing(second.terminal, line); } // Both turns are recorded rows rather than a live process's readings, and - // both Plan reviews are retained where they were asked. + // both Plan reviews are saved where they were asked. expect(occurrences(second.terminal, "completed, recorded")).toBe(2); expect( yield* answeredUnder(second.terminal, "component Plan", "answered @executablemd/cli/Plan"), ).toBe(2); - // The program's own two questions are retained with the answers they + // The program's own two questions are saved with the answers they // took. Their positions carry no path — generated source is not a file — // so they are shown under the fragment that produced them. yield* click(second.terminal, "generated generated"); yield* settled(40); // The answers themselves are read from the reading above, which shows - // them in full; what this counts is that each question is retained under + // them in full; what this counts is that each question is saved under // the fragment that asked it, at the position it was written. expect(occurrences(second.terminal, "answered 4:1")).toBe(1); expect(occurrences(second.terminal, "answered 12:1")).toBe(1); @@ -680,7 +680,7 @@ describe("J3 — durable truth, and a cold process over it", () => { expect(yield* history(hostRoot)).toBe(before); }); - it("EC1: two entries of retained Agent work reopen cold, asking nobody", function* () { + it("EC1: two entries of saved Agent work reopen cold, asking nobody", function* () { const hostRoot = yield* untilResolved(mkdtemp(join(tmpdir(), "xmd-repl-journey-"))); const live = createFakeAcp(); live.script({ reply: "ONE-DONE" }); @@ -902,7 +902,7 @@ describe("J3 — durable truth, and a cold process over it", () => { // admission, the turn's publication, and the root settling. yield* click(terminal, "[history]"); yield* settled(40); - // The categories a retained position reads as. Re-anchored for #881 + // The categories a saved position reads as. Re-anchored for #881 // PR 3: a drawer row was the checkpoint's own label and is now its place // in the recorded order and what kind of position it is — identical // wording live and historical, so neither route invents a richer one. @@ -1105,14 +1105,14 @@ describe("J2 — three conversations at once, through the terminal", () => { // focused control stay exactly where the person left them. held.start(PLAN_IT); held.deltas(PLAN_IT); - yield* awaiting("the third turn was never retained", function* () { + yield* awaiting("the third turn was never saved", function* () { return ( recorded(yield* journal(hostRoot)).filter((kind) => kind === "agent_prompt").length === 3 ); }); expect(sessionOf(terminal)).toBe(standing); expect(focusedRow(terminal)).toBe(focused); - // And the filter still holds: the others are retained and not shown, + // And the filter still holds: the others are saved and not shown, // because this screen is showing one. expect(childrenShown(terminal)).toHaveLength(1); @@ -1134,7 +1134,7 @@ describe("J2 — three conversations at once, through the terminal", () => { expect(conversationRows(terminal, digest).length).toBeGreaterThan(offered); // Three turns, three records, one per conversation. - yield* awaiting("the three turns were never all retained", function* () { + yield* awaiting("the three turns were never all saved", function* () { return ( recorded(yield* journal(hostRoot)).filter((kind) => kind === "agent_prompt").length === 3 ); @@ -1208,7 +1208,7 @@ describe("J2 — a filter and a draft across a resize (#875 R1)", () => { held.start(asked); held.deltas(asked); } - yield* awaiting("the three turns were never all retained", function* () { + yield* awaiting("the three turns were never all saved", function* () { return ( recorded(yield* journal(hostRoot)).filter((kind) => kind === "agent_prompt").length === 3 ); @@ -1386,7 +1386,7 @@ describe("J2 — what filtering Sessions leaves on the screen (#875 R1)", () => held.start(asked); held.deltas(asked); } - yield* awaiting("the three turns were never all retained", function* () { + yield* awaiting("the three turns were never all saved", function* () { return ( recorded(yield* journal(hostRoot)).filter((kind) => kind === "agent_prompt").length === 3 ); @@ -2344,7 +2344,7 @@ function occurrences(terminal: Terminal, text: string): number { * * #881 PR 2 gave the Transcript the selected entry's reading, so the records of * every scope no longer appear there at once; the answers a nested scope - * retained are read by selecting that scope, which is where the product says + * saved are read by selecting that scope, which is where the product says * they were asked. Selection is the behavior being tested: a row counted under the wrong scope * would be an answer attributed to somewhere it never happened. */ @@ -2475,7 +2475,7 @@ function routeOf(location: string | undefined): ReplRoute { return decoded.value; } -/** Whether this reading is a retained position rather than the live head. */ +/** Whether this reading is a saved position rather than the live head. */ function historicalOn(terminal: Terminal): boolean { return shows(terminal, "[live]"); } diff --git a/packages/cli/tests/repl-boundaries.test.ts b/packages/cli/tests/repl-boundaries.test.ts index ae576fa92..7becc863d 100644 --- a/packages/cli/tests/repl-boundaries.test.ts +++ b/packages/cli/tests/repl-boundaries.test.ts @@ -297,7 +297,7 @@ describe("REPL boundaries: what production code cannot reach", () => { }); it("S1: no REPL record type exists in production", function* () { - // Every retained line is an ordinary durable event. A record shape of this + // Every saved line is an ordinary durable event. A record shape of this // slice's own would be a second protocol nobody else can read. for (const path of yield* authored()) { const text = yield* code(path); diff --git a/packages/cli/tests/repl-composition.test.ts b/packages/cli/tests/repl-composition.test.ts index fa529880e..245e24609 100644 --- a/packages/cli/tests/repl-composition.test.ts +++ b/packages/cli/tests/repl-composition.test.ts @@ -501,8 +501,8 @@ describe("REPL composition: a description is issued, not written", () => { const definition = readComponent(Panel); // Every record and list the handles retain, walked. - for (const retained of reachableFrom([described, definition])) { - expect(Object.isFrozen(retained)).toBe(true); + for (const saved of reachableFrom([described, definition])) { + expect(Object.isFrozen(saved)).toBe(true); } expect(Object.isFrozen(described)).toBe(true); expect(Object.isFrozen(definition)).toBe(true); diff --git a/packages/cli/tests/repl-entries.test.ts b/packages/cli/tests/repl-entries.test.ts index b7e574770..c3a8c8526 100644 --- a/packages/cli/tests/repl-entries.test.ts +++ b/packages/cli/tests/repl-entries.test.ts @@ -100,7 +100,7 @@ const FIRST = [ * One entry that publishes a root value and then fails. * * Two blocks, so the journal holds one durable publication *before* the - * failure: `token` is retained, and `lost` is a name the failing block never + * failure: `token` is saved, and `lost` is a name the failing block never * got to publish. */ const FAILING = [ @@ -156,7 +156,7 @@ const EDITS_INHERITED = [ */ const PUBLISHES_PROPS = [ "```js eval", - 'const props = "retained";', + 'const props = "saved";', 'const token = "gamma";', "```", "", @@ -422,18 +422,18 @@ describe("REPL entries: the segments one physical journal holds", () => { const physical = new InMemoryStream(events); // Settled, because its own range holds its terminal close. - const closed = new EntrySegmentStream(physical, { retained: second.events, final: true }); + const closed = new EntrySegmentStream(physical, { saved: second.events, final: true }); expect(closed.settled).toBe(true); expect(yield* refusedAppend(closed, first.events[1])).toContain("already settled"); // Not final, because another entry already follows it in the file. - const earlier = new EntrySegmentStream(physical, { retained: first.events, final: false }); + const earlier = new EntrySegmentStream(physical, { saved: first.events, final: false }); expect(yield* refusedAppend(earlier, first.events[1])).toContain("not the last one"); // And an open final segment closes itself on its own terminal close, so the // append after it is refused for the first reason rather than accepted. const live = new EntrySegmentStream(physical, { - retained: first.events.slice(0, -1), + saved: first.events.slice(0, -1), final: true, }); expect(live.settled).toBe(false); @@ -748,7 +748,7 @@ describe("REPL entries: what each entry owns and what the next one inherits", () expect(value(after.entries[1].bindings, "seen")).toBe("draft/build/2"); }); - it("EP1: a retained `props` is the entry's own and reaches no successor", function* () { + it("EP1: a saved `props` is the entry's own and reaches no successor", function* () { const physical = new InMemoryStream(); const run = yield* runEntry(physical, PUBLISHES_PROPS); const events = yield* physical.readAll(); @@ -758,7 +758,7 @@ describe("REPL entries: what each entry owns and what the next one inherits", () // It really was published: the run succeeded and the record holds the name. expect(run.failure).toBe(undefined); expect(only.scope.bindings.map((binding) => binding.name)).toContain("props"); - expect(value(only.scope.bindings, "props")).toBe("retained"); + expect(value(only.scope.bindings, "props")).toBe("saved"); // And it is absent from the effective bindings and from the record a next // entry would start from — rather than listed there and then silently @@ -773,7 +773,7 @@ describe("REPL entries: what each entry owns and what the next one inherits", () expect(second.output).toContain("Two: gamma/object"); }); - it("EP1: a retained export name no binding can have refuses projection", function* () { + it("EP1: a saved export name no binding can have refuses projection", function* () { const physical = new InMemoryStream(); yield* runEntry(physical, FIRST); const events = yield* physical.readAll(); @@ -1227,7 +1227,7 @@ describe("REPL entries: one session, one entry task, in turn", () => { const [first, second] = session.model.entries; expect(second.terminal?.output).toContain("Three: alpha/first"); - // Exactly the two names the first entry retained, and nothing else: the + // Exactly the two names the first entry saved, and nothing else: the // record handed to the second entry is the one a cold command derives from // the same bytes, not a reading of what this process had in memory. expect(entryInitialBindings(session.model, second)).toEqual({ @@ -1939,7 +1939,7 @@ describe("REPL entries: the draft, the gate and the position", () => { accepted(yield* session.submit(INHERITING)); yield* session.join(); - // The source the journal retained is the draft, character for character. + // The source the journal saved is the draft, character for character. const admittedEntry = session.model.entries[1]; expect(admittedEntry?.source).toBe(INHERITING); // And the key is the one the session really assigned, not a constant. @@ -2366,7 +2366,7 @@ function* sessionsOf(view: ReplView): Operation { beforeAll(() => useTempFileCompiler()); - it("EU1: each entry shows its own retained transcript, and no other entry's", function* () { + it("EU1: each entry shows its own saved transcript, and no other entry's", function* () { const physical = new InMemoryStream(); yield* runEntry(physical, saying("ALPHA-ONE")); yield* runEntry(physical, saying("BRAVO-TWO")); @@ -2512,7 +2512,7 @@ describe("REPL entries: restoring an answer at a narrow size (#875 R1)", () => { expect(keyedBy(tree)).toBe(`entry:${entry.key}`); // The claim is spent by the commit that satisfied it — a restoration is one - // claim, not a standing one — and the reveal it caused is retained in the + // claim, not a standing one — and the reveal it caused is saved in the // offset this process holds. const committed = (yield* contextOf(view)).admission; const settled = focusSettled(view, `entry:${entry.key}`, committed); @@ -2720,7 +2720,7 @@ describe("REPL entries: what each prefix of a two-entry history shows", () => { // [3] At its terminal position: the same row, now carrying its outcome. expect(yield* catalogAt(terminal)).toEqual(["1. [ok] entry-1", "2. [ok] entry-2"]); - // Each prefix shows only what it retained. The second entry's output is in + // Each prefix shows only what it saved. The second entry's output is in // none of the readings before its own close. const linesAt = function* (marker: string): Operation { const state = frozenAt(drafting(initialState(EXECUTION), "still typing"), marker); @@ -2729,7 +2729,7 @@ describe("REPL entries: what each prefix of a two-entry history shows", () => { // The Output half alone. Re-anchored for #881 PR 2: the reading also shows // the source each prefix admitted, and at the admission checkpoint that // source is the second entry's own — so the claim being made here, that no - // prefix shows output it had not retained, is read off the half that + // prefix shows output it had not saved, is read off the half that // carries output. const outputAt = function* (marker: string): Operation { const state = frozenAt(drafting(initialState(EXECUTION), "still typing"), marker); @@ -2742,7 +2742,7 @@ describe("REPL entries: what each prefix of a two-entry history shows", () => { expect(yield* outputAt(terminal)).toContain("BRAVO-TWO"); // The draft is current route state throughout: it is not a thing a prefix - // retained, so freezing the durable view does not freeze it. + // saved, so freezing the durable view does not freeze it. for (const marker of [before, admission, terminal]) { const state = frozenAt(drafting(initialState(EXECUTION), "still typing"), marker); const view = reading(state, projected(events, marker), NOTHING_LIVE, WIDE); @@ -2797,7 +2797,7 @@ describe("REPL entries: a third entry inherits the file, not the run before it", // Entry 1 publishes a nested value. Entry 2 inherits it and edits what it // inherited — ordinary mutation of an ordinary binding, which publishes // nothing, so the two readings of `plan` diverge from here on: the file - // holds what Entry 1 retained and this process holds what Entry 2 made of + // holds what Entry 1 saved and this process holds what Entry 2 made of // it. const session = opened( yield* submitReplEntry({ execution: holder, source: PUBLISHES_NESTED }), @@ -3148,7 +3148,7 @@ describe("REPL entries: what a frozen position says is unavailable", () => { * opens. Nothing here is produced by elapsed time. The only timing is the bounded * wait that keeps a genuine deadlock from reporting as a hang. */ -describe("REPL entries: a retained close while the task is still coming down", () => { +describe("REPL entries: a saved close while the task is still coming down", () => { beforeAll(() => useTempFileCompiler()); /** The contextual guidance this view describes. */ diff --git a/packages/cli/tests/repl-forms.test.ts b/packages/cli/tests/repl-forms.test.ts index 255f7dc90..f03c110f4 100644 --- a/packages/cli/tests/repl-forms.test.ts +++ b/packages/cli/tests/repl-forms.test.ts @@ -191,7 +191,7 @@ describe("F1 — the bounded language is exact", () => { expect(Object.isFrozen(field)).toBe(true); } expect(Object.isFrozen(form.condition)).toBe(true); - // Nothing the caller owns was retained or frozen on its behalf. + // Nothing the caller owns was saved or frozen on its behalf. expect(Object.isFrozen(PLAN_SCHEMA)).toBe(false); }); @@ -1126,7 +1126,7 @@ describe("F3 — complete content and reachable navigation", () => { }); describe("F3 — focus returns to the invocation, not to where the drawer came from", () => { - // The retained row focus lands on exists only once an answer is recorded, so + // The saved row focus lands on exists only once an answer is recorded, so // the model here is a real execution's journal rather than a shape written by // this test. beforeAll(() => useTempFileCompiler()); @@ -1150,7 +1150,7 @@ describe("F3 — focus returns to the invocation, not to where the drawer came f const appended = elicitAppends(holder); // An answer this test did not cause, carrying the same value the one under - // test will carry. What excludes it is that it was already retained, not + // test will carry. What excludes it is that it was already saved, not // that it looks different. const first = yield* waiting(session, "the first question"); expect(first.message).toContain("First"); @@ -1214,13 +1214,13 @@ describe("F3 — focus returns to the invocation, not to where the drawer came f expect(third.submit({ decision: "Stop" }).kind).toBe("answered"); yield* appended(3); - const retained = answers(session.model); - expect(retained).toHaveLength(3); - const caused = retained[1]; - const before = retained[0]; - const newest = retained[2]; + const saved = answers(session.model); + expect(saved).toHaveLength(3); + const caused = saved[1]; + const before = saved[0]; + const newest = saved[2]; if (caused === undefined || before === undefined || newest === undefined) { - throw new Error("the entry retained three answers"); + throw new Error("the entry saved three answers"); } // The one it did not cause has the same answer; the newest one is somebody // else's. Identity here is both, or neither would be enough. @@ -1266,7 +1266,7 @@ describe("F3 — focus returns to the invocation, not to where the drawer came f }); it("F3: closing without answering moves focus to the invocation still asking", function* () { - const recorded = yield* retained(); + const recorded = yield* saved(); const asked = yield* askingFor(CONFIRM_SCHEMA); const live = asking(asked.question); const open = withScope(opened(live), recorded.scope); @@ -1295,7 +1295,7 @@ describe("F3 — focus returns to the invocation, not to where the drawer came f }); it("F3: without the claim the drawer going leaves focus on an unrelated control", function* () { - const recorded = yield* retained(); + const recorded = yield* saved(); const asked = yield* askingFor(CONFIRM_SCHEMA); const live = asking(asked.question); const open = withScope(opened(live), recorded.scope); @@ -1320,7 +1320,7 @@ describe("F3 — focus returns to the invocation, not to where the drawer came f }); /** One real journal with an answer in it, and the scope that shows it. */ -function* retained(): Operation<{ +function* saved(): Operation<{ readonly model: ReplModel; readonly scope: string; readonly marker: string; @@ -1484,7 +1484,7 @@ function elicitAppends(holder: ReplExecution): (count: number) => Operation { @@ -1630,7 +1630,7 @@ describe("F3 — every placed modal control is in the ring", () => { const tree = yield* useReplTree(); // A real one-entry history under it, so the row names the entry the question // belongs to rather than a readiness with no entry in it. - const recorded = yield* retained(); + const recorded = yield* saved(); // One window action first. The drawer's top rule and side inset are part of // its measured rectangle, so at 72x20 the content window is one row shorter // than this form's rows and `[submit]` is in the next one. Advanced through @@ -1744,7 +1744,7 @@ describe("F2 — an invalid answer is not a lifecycle refusal", () => { // — is reachable inside the drawer's window rather than past the end of it. const asked = yield* askingFor(CONFIRM_SCHEMA); const live = asking(asked.question); - const recorded = yield* retained(); + const recorded = yield* saved(); // What the schema said about one field, carried where the reducer carries it. const invalid = Object.freeze({ @@ -1787,7 +1787,7 @@ describe("F2 — an invalid answer is not a lifecycle refusal", () => { }); }); -describe("F3 — the drawers that show a retained reading (#875 R1)", () => { +describe("F3 — the drawers that show a saved reading (#875 R1)", () => { /** A value long enough that no drawer can show all of it at once. */ const LONG_VALUE = Object.freeze( Array.from({ length: 60 }, (_unused, at) => `line-${String(at).padStart(2, "0")}`), @@ -1986,7 +1986,7 @@ describe("F3 — the drawers that show a retained reading (#875 R1)", () => { expect(rows).toContain(`drawer:value:${LONG_VALUE.length - 1}`); }); - it("TL3: scrolling a retained reading changes that offset and nothing else", function* () { + it("TL3: scrolling a saved reading changes that offset and nothing else", function* () { const engine = yield* useReplRenderer(NARROW); const model = twoScopes(); const before = openBinding("entry-1", "plan"); @@ -2170,7 +2170,7 @@ describe("F3 — a narrow frame offers no inspection control (#875 R1)", () => { } it("TL11: a recorded binding and answer are in a wide frame and in no part of a narrow one", function* () { - const recorded = yield* retained(); + const recorded = yield* saved(); const live = asking(undefined); const state = withScope(initialState("frames"), recorded.scope); @@ -2223,7 +2223,7 @@ describe("F3 — a narrow frame offers no inspection control (#875 R1)", () => { }); it("TL11: dismissing a recorded answer at a narrow size lands focus on a drawn control", function* () { - const recorded = yield* retained(); + const recorded = yield* saved(); const live = asking(undefined); // The drawer a wider window opened, still open after the window narrowed. const open = Object.freeze({ diff --git a/packages/cli/tests/repl-history.test.ts b/packages/cli/tests/repl-history.test.ts index f9ce34a1e..cb15a5928 100644 --- a/packages/cli/tests/repl-history.test.ts +++ b/packages/cli/tests/repl-history.test.ts @@ -11,7 +11,7 @@ * * - **H1/H2** navigation is the whole file while content is one prefix, and * nothing of a later position's payload crosses; - * - **H3** the head is derived from retained facts and work this process + * - **H3** the head is derived from saved facts and work this process * actually owns, in the frozen precedence; * - **H4** the five rows are allocated by measurement: first and last * positions, deterministic collisions, the entry/minor hierarchy, caption @@ -56,7 +56,7 @@ const CROWD = fileURLToPath(new URL("./fixtures/repl/issue-881/", import.meta.ur /** * One entry that admits this many component scopes, in recorded order. * - * Each invocation admits a scope and each admitted scope is one retained + * Each invocation admits a scope and each admitted scope is one saved * position, so this is how many positions the rail has to place. Built here * rather than read from `history-crowd.md` so a case can say how many it * needs; the fixture file is the same shape at two hundred. @@ -338,9 +338,9 @@ describe("H4 — the five rows are allocated by measurement", () => { const facts = (over: Partial[0]>) => headOf({ entries: 1, outcome: false, working: false, paused: false, pausing: false, ...over }); -describe("H3 — the head is derived from retained facts and owned work", () => { +describe("H3 — the head is derived from saved facts and owned work", () => { it("takes the frozen precedence, and never infers a pause", function* () { - // 1 settling beats everything: an outcome is retained and the work this + // 1 settling beats everything: an outcome is saved and the work this // process owns has not come down yet. expect(facts({ outcome: true, working: true, paused: true, pausing: true })).toBe("settling"); expect(facts({ working: true, paused: true, pausing: true })).toBe("paused"); @@ -371,7 +371,7 @@ describe("H3 — the head a real session publishes", () => { it("is empty before an entry, live while one runs, settled once it has", function* () { const session = opened(yield* openReplSession({ execution: execution() })); - // Nothing retained and nothing owned. + // Nothing saved and nothing owned. expect(session.navigation.head).toBe("empty"); expect(session.navigation.checkpoints).toEqual([]); @@ -381,13 +381,13 @@ describe("H3 — the head a real session publishes", () => { yield* session.join(); yield* sleep(0); - // The outcome is retained and nothing of the entry is still standing. + // The outcome is saved and nothing of the entry is still standing. expect(session.navigation.head).toBe("settled"); expect(session.navigation.checkpoints.map((one) => one.kind)).toContain("entry"); expect(session.navigation.checkpoints.map((one) => one.kind)).toContain("terminal"); }); - it("is SETTLING while the outcome is retained and the entry is still coming down", function* () { + it("is SETTLING while the outcome is saved and the entry is still coming down", function* () { // The window a reader watching a run end is actually in: the root closed, // and this process still owns what the entry acquired. A head taken from // the root Close alone would call this finished. @@ -644,7 +644,7 @@ describe("H1 — two positions the rail draws as one mark select different conte })); }); -describe("H1/H5 — every retained position keeps its own identity", () => { +describe("H1/H5 — every saved position keeps its own identity", () => { it("says what each kind is, with its full ordinal and entry number", function* () { const navigation = history([ "entry", diff --git a/packages/cli/tests/repl-journey.test.ts b/packages/cli/tests/repl-journey.test.ts index 678657605..b55a468a8 100644 --- a/packages/cli/tests/repl-journey.test.ts +++ b/packages/cli/tests/repl-journey.test.ts @@ -607,7 +607,7 @@ function focusedOn(terminal: Terminal, label: string): boolean { // focused and the one being read, and the two markers are independent. const after = line.slice(at + 1).trimStart(); const beyond = after.startsWith("* ") ? after.slice(2) : after; - // And past a History row's own ordinal. #881 PR 3 gives every retained + // And past a History row's own ordinal. #881 PR 3 gives every saved // position its number in the full list — `12 · Entry 3 admitted` — which // is another prefix the product puts there, like the two above. const named = beyond.replace(/^\d+ \u00b7 /, ""); @@ -658,7 +658,7 @@ function exitRoute(): ExitRoute { }; } -/** Whether this reading is a retained position rather than the live head. */ +/** Whether this reading is a saved position rather than the live head. */ function historicalOn(terminal: Terminal): boolean { return screenOf(terminal).some((line) => line.includes("[live]")); } @@ -1317,7 +1317,7 @@ describe("REPL journey: one entry, from raw bytes", () => { // The question announces itself and opens nothing. Activating that // announcement is what opens the drawer, and the drawer then shows the - // retained schema as a form: one field, its choices. + // saved schema as a form: one field, its choices. expect(shows(terminal, "decision: approve | decline")).toBe(false); yield* openQuestion(terminal); expect(shows(terminal, "decision: approve | decline")).toBe(true); @@ -1343,7 +1343,7 @@ describe("REPL journey: one entry, from raw bytes", () => { }); // The Sessions surface is still there, and still explicitly empty. - expect(shows(terminal, "(none retained)")).toBe(true); + expect(shows(terminal, "(none saved)")).toBe(true); expect(location).toBeDefined(); expect(terminal.resets).toBe(1); }); @@ -1390,13 +1390,13 @@ describe("REPL journey: one entry, from raw bytes", () => { expect(first.terminal.resets).toBe(1); expect(first.terminal.readers).toBe(0); - const retained = root; - expect(retained).toBeDefined(); - if (retained === undefined) { + const saved = root; + expect(saved).toBeDefined(); + if (saved === undefined) { throw new Error("the first process created a repository"); } const execution = files[0].replace(/\.jsonl$/, ""); - const lines = yield* records(retained, files[0]); + const lines = yield* records(saved, files[0]); // A location that selects something meaningful: a structural scope inside the // entry, a historical position, and a recorded drawer. @@ -1426,7 +1426,7 @@ describe("REPL journey: one entry, from raw bytes", () => { binding === undefined || marker === undefined ) { - throw new Error("the retained history holds an entry, a nested scope, a binding and markers"); + throw new Error("the saved history holds an entry, a nested scope, a binding and markers"); } // A drawer names something inside the scope the location selects, which is @@ -1451,7 +1451,7 @@ describe("REPL journey: one entry, from raw bytes", () => { yield* second.install(); yield* immediateClock(); yield* installReplHost({ - dataRoot: () => retained, + dataRoot: () => saved, identify: () => { throw new Error("a reopened execution mints no identifier"); }, @@ -1473,7 +1473,7 @@ describe("REPL journey: one entry, from raw bytes", () => { // The same topology, the same binding value, the same transcript and the // same recorded answer — read from the file, not re-run. - // The drawer the location named is open on the retained value. + // The drawer the location named is open on the saved value. expect(shows(second.terminal, "[close]")).toBe(true); // Out from under the drawer first: a modal covers what it is in front of, so @@ -1501,7 +1501,7 @@ describe("REPL journey: one entry, from raw bytes", () => { expect(performed?.compiles).toBe(0); expect(performed?.asked).toBe(0); // And nothing was appended: the file is exactly what the first process left. - expect(yield* records(retained, files[0])).toEqual(lines); + expect(yield* records(saved, files[0])).toEqual(lines); expect(second.terminal.resets).toBe(1); }); @@ -1522,7 +1522,7 @@ describe("REPL journey: one entry, from raw bytes", () => { }); yield* settled(); - // One retained execution, created exclusively, and named opaquely. + // One saved execution, created exclusively, and named opaquely. const files = yield* histories(root); expect(files).toHaveLength(1); const execution = files[0].replace(/\.jsonl$/, ""); @@ -1532,7 +1532,7 @@ describe("REPL journey: one entry, from raw bytes", () => { // The screen says where it is, what it holds and what it is waiting for. expect(shows(terminal, "Sessions")).toBe(true); - expect(shows(terminal, "(none retained)")).toBe(true); + expect(shows(terminal, "(none saved)")).toBe(true); expect(shows(terminal, "Entries")).toBe(true); expect(shows(terminal, "(not submitted)")).toBe(true); @@ -1727,7 +1727,7 @@ describe("REPL journey: leaving", () => { const entry = projected.value.entries[0]?.scope; const marker = projected.value.checkpoints[projected.value.checkpoints.length - 1]?.marker; if (entry === undefined || marker === undefined) { - throw new Error("the retained history holds an entry and at least one position"); + throw new Error("the saved history holds an entry and at least one position"); } const location = encodeLocation({ @@ -2295,11 +2295,11 @@ describe("REPL journey: the whole of it, from raw bytes", () => { yield* activate(terminal, "answered"); // The reading this state *is*, said on the screen: an entry selected, a - // recorded position being read, and the retained answer's drawer over it. + // recorded position being read, and the saved answer's drawer over it. expect(selectedEntryOn(terminal)).toContain("entry-1"); expect(historicalOn(terminal)).toBe(true); expect(shows(terminal, "[close]")).toBe(true); - // The whole retained schema and the whole retained answer, reached through + // The whole saved schema and the whole saved answer, reached through // the drawer's own window. // // This drawer scrolls (#875). It used to describe every row at once and @@ -2309,11 +2309,11 @@ describe("REPL journey: the whole of it, from raw bytes", () => { // longer "every line is on screen together" but the stronger one: every // line is **reachable**, and the rows that are not visible are not mounted. const { first: firstFrame, reached } = yield* drawerContent(terminal); - const retainedRows = [ + const savedRows = [ ...JSON.stringify(SCHEMA, undefined, 2).split("\n"), ...JSON.stringify(ANSWER, undefined, 2).split("\n"), ].map((line) => line.trim()); - for (const line of retainedRows) { + for (const line of savedRows) { expect(reached).toContain(line); } // And it really was a window rather than one tall drawer. This record is @@ -2346,11 +2346,11 @@ describe("REPL journey: the whole of it, from raw bytes", () => { } }); - const retained = root; - if (retained === undefined || captured === undefined) { + const saved = root; + if (saved === undefined || captured === undefined) { throw new Error("the first process ran and reported a location"); } - const lines = yield* records(retained, files[0]); + const lines = yield* records(saved, files[0]); expect(lines.length).toBeGreaterThan(0); // 10. A fresh host, with nothing but that location and the file. @@ -2360,7 +2360,7 @@ describe("REPL journey: the whole of it, from raw bytes", () => { yield* second.install(); yield* immediateClock(); yield* installReplHost({ - dataRoot: () => retained, + dataRoot: () => saved, identify: () => { throw new Error("a reopened execution mints no identifier"); }, @@ -2386,7 +2386,7 @@ describe("REPL journey: the whole of it, from raw bytes", () => { expect(selectedEntryOn(second.terminal)).toContain("entry-1"); expect(historicalOn(second.terminal)).toBe(true); - // The same retained question: the whole schema and the whole answer, not a + // The same saved question: the whole schema and the whole answer, not a // label that happens to contain the word. Reached through the drawer's own // window, which a reopened process opens at its first row — how far a // window is scrolled is process-local and no location carries it. @@ -2443,7 +2443,7 @@ describe("REPL journey: the whole of it, from raw bytes", () => { expect(performed?.reads ?? ["unmeasured"]).toEqual([]); expect(performed?.compiles).toBe(0); expect(performed?.asked).toBe(0); - expect(yield* records(retained, files[0])).toEqual(lines); + expect(yield* records(saved, files[0])).toEqual(lines); expect(second.terminal.resets).toBe(1); expect(second.terminal.readers).toBe(0); expect(second.terminal.listeners).toBe(0); @@ -2735,7 +2735,7 @@ describe("REPL journey: the same product at every size", () => { // finding one inside the drawer means it showed through. // Re-anchored for #881 PR 2: the transcript's record rows became the // entry's reading, so these are two lines of what that reading shows. - // Both belong to it and to nothing this drawer lists — the retained + // Both belong to it and to nothing this drawer lists — the saved // positions name `summarySource` and ` admitted`, so // neither `Source` nor `Checklist` would discriminate anything. const underneath = ["### Ship the REPL", "2 steps remain."]; @@ -2868,7 +2868,7 @@ describe("REPL journey: what it settles before it acts", () => { it("J1: a route naming a scope the history never had refuses before anything replays", function* () { const source = yield* referenceSource(); const first = recordingTerminal(); - let retained = ""; + let saved = ""; let files: string[] = []; let entryKey = ""; @@ -2879,7 +2879,7 @@ describe("REPL journey: what it settles before it acts", () => { yield* scoped(function* (): Operation { yield* first.install(); yield* immediateClock(); - retained = yield* useTemporaryHost(); + saved = yield* useTemporaryHost(); const exited = exitRoute(); const running = yield* spawn(function* (): Operation { @@ -2890,7 +2890,7 @@ describe("REPL journey: what it settles before it acts", () => { exited.settle(ran.value); }); yield* untilDrawn(first.terminal); - files = yield* histories(retained); + files = yield* histories(saved); first.terminal.bytes(BYTES.encode(source)); yield* settled(60); @@ -2902,11 +2902,11 @@ describe("REPL journey: what it settles before it acts", () => { yield* running; }); - const model = yield* projectionOf(retained, files[0]); + const model = yield* projectionOf(saved, files[0]); expect(model.entries[0]?.scope).toBeDefined(); entryKey = model.entries[0]?.key ?? ""; expect(model.settled).toBe(false); - const lines = yield* records(retained, files[0]); + const lines = yield* records(saved, files[0]); // A URL a person could plausibly type: the execution is real, the entry is // real, and one segment of the path is a typo. @@ -2928,7 +2928,7 @@ describe("REPL journey: what it settles before it acts", () => { yield* second.install(); yield* immediateClock(); yield* installReplHost({ - dataRoot: () => retained, + dataRoot: () => saved, identify: () => { throw new Error("a reopened execution mints no identifier"); }, @@ -2961,13 +2961,13 @@ describe("REPL journey: what it settles before it acts", () => { // what the first process left behind. expect(performed?.asked).toBe(0); expect(performed?.compiles).toBe(0); - expect(yield* records(retained, files[0])).toEqual(lines); + expect(yield* records(saved, files[0])).toEqual(lines); }); it("J1: a stale live-question URL refuses against a finished execution", function* () { const source = yield* referenceSource(); const first = recordingTerminal(); - let retained = ""; + let saved = ""; let files: string[] = []; // A history that ran to the end: the question was asked, answered and @@ -2975,7 +2975,7 @@ describe("REPL journey: what it settles before it acts", () => { yield* scoped(function* (): Operation { yield* first.install(); yield* immediateClock(); - retained = yield* useTemporaryHost(); + saved = yield* useTemporaryHost(); const exited = exitRoute(); const running = yield* spawn(function* (): Operation { @@ -2986,7 +2986,7 @@ describe("REPL journey: what it settles before it acts", () => { exited.settle(ran.value); }); yield* untilDrawn(first.terminal); - files = yield* histories(retained); + files = yield* histories(saved); first.terminal.bytes(BYTES.encode(source)); yield* settled(60); @@ -3001,9 +3001,9 @@ describe("REPL journey: what it settles before it acts", () => { yield* running; }); - const model = yield* projectionOf(retained, files[0]); + const model = yield* projectionOf(saved, files[0]); expect(model.settled).toBe(true); - const lines = yield* records(retained, files[0]); + const lines = yield* records(saved, files[0]); // The URL somebody kept from while the question was up. The execution it // names is real and the drawer it names was real — and the question is gone, @@ -3027,7 +3027,7 @@ describe("REPL journey: what it settles before it acts", () => { yield* second.install(); yield* immediateClock(); yield* installReplHost({ - dataRoot: () => retained, + dataRoot: () => saved, identify: () => { throw new Error("a reopened execution mints no identifier"); }, @@ -3053,7 +3053,7 @@ describe("REPL journey: what it settles before it acts", () => { expect(outcome?.ok).toBe(false); expect(performed?.asked).toBe(0); expect(performed?.compiles).toBe(0); - expect(yield* records(retained, files[0])).toEqual(lines); + expect(yield* records(saved, files[0])).toEqual(lines); }); it("J1: an answered history with its root still open refuses the same URL", function* () { @@ -4103,11 +4103,11 @@ describe("REPL journey: a selection, a position and a draft across a resize", () expect(stood.value.draft).toBe(DRAFTED); } - const retained = root; - if (retained === undefined || standing === undefined) { + const saved = root; + if (saved === undefined || standing === undefined) { throw new Error("the first process created a repository and showed a location"); } - const path = join(retained, "xmd", "repl", files[0]); + const path = join(saved, "xmd", "repl", files[0]); const before = yield* until(readFile(path, "utf8")); // A cold process over that exact location: the same reading, with no work @@ -4117,7 +4117,7 @@ describe("REPL journey: a selection, a position and a draft across a resize", () yield* scoped(function* (): Operation { yield* second.install(); yield* immediateClock(); - yield* reopening(retained); + yield* reopening(saved); performed = yield* countPerformed(); const coldRoute = exitRoute(); @@ -4525,8 +4525,8 @@ describe("REPL journey: a cold process at an early position", () => { standing = exited.location(); }); - const retained = root; - if (retained === undefined || standing === undefined) { + const saved = root; + if (saved === undefined || standing === undefined) { throw new Error("the first process created a repository and showed a location"); } // What the location names: the early position and the draft, and nothing @@ -4537,16 +4537,16 @@ describe("REPL journey: a cold process at an early position", () => { expect(stood.value.draft).toBe(DRAFTED); expect(stood.value.at).toBeDefined(); } - const path = join(retained, "xmd", "repl", files[0]); + const path = join(saved, "xmd", "repl", files[0]); const before = yield* until(readFile(path, "utf8")); - const retainedFiles = yield* histories(retained); + const savedFiles = yield* histories(saved); const second = recordingTerminal({ columns: 160, rows: 36 }); let performed: Performed | undefined; yield* scoped(function* (): Operation { yield* second.install(); yield* immediateClock(); - yield* reopening(retained); + yield* reopening(saved); performed = yield* countPerformed(); const terminal = second.terminal; @@ -4617,7 +4617,7 @@ describe("REPL journey: a cold process at an early position", () => { expect(performed?.reads.filter((one) => one.endsWith(".md"))).toEqual([]); expect(performed?.asked).toBe(0); // And nothing was written: the same files, byte for byte. - expect(yield* histories(retained)).toEqual(retainedFiles); + expect(yield* histories(saved)).toEqual(savedFiles); expect(yield* until(readFile(path, "utf8"))).toBe(before); expect(second.terminal.resets).toBe(1); }); @@ -4659,15 +4659,15 @@ describe("REPL journey: a cold process over a multi-entry journal", () => { expect(first.terminal.resets).toBe(1); expect(first.terminal.readers).toBe(0); - const retained = root; - if (retained === undefined) { + const saved = root; + if (saved === undefined) { throw new Error("the first process created a repository"); } - const path = join(retained, "xmd", "repl", files[0]); + const path = join(saved, "xmd", "repl", files[0]); const before = yield* until(readFile(path, "utf8")); const execution = files[0].replace(/\.jsonl$/, ""); - const live = yield* projectionOf(retained, files[0]); + const live = yield* projectionOf(saved, files[0]); expect(live.entries.map((entry) => entry.key)).toEqual(["entry-1", "entry-2"]); // A location that names the *second* entry, so what comes back has to be a @@ -4689,7 +4689,7 @@ describe("REPL journey: a cold process over a multi-entry journal", () => { yield* second.install(); yield* immediateClock(); yield* installReplHost({ - dataRoot: () => retained, + dataRoot: () => saved, identify: () => { throw new Error("a reopened execution mints no identifier"); }, @@ -4718,7 +4718,7 @@ describe("REPL journey: a cold process over a multi-entry journal", () => { // The value the first entry published is still what the second inherited, // read back from the file rather than from anything this process ran. - const cold = yield* projectionOf(retained, files[0]); + const cold = yield* projectionOf(saved, files[0]); expect(cold.entries[1]?.bindings.find((binding) => binding.name === "carried")?.value).toBe( "alpha-again", ); @@ -5327,7 +5327,7 @@ describe("REPL first use: UI1 selection", () => { // Selecting an entry does not filter the chronology this execution has. yield* activate(terminal, "Sessions"); - expect(shows(terminal, "(none retained)")).toBe(true); + expect(shows(terminal, "(none saved)")).toBe(true); terminal.end(); yield* running; diff --git a/packages/cli/tests/repl-model.test.ts b/packages/cli/tests/repl-model.test.ts index 849b7866d..c03daef98 100644 --- a/packages/cli/tests/repl-model.test.ts +++ b/packages/cli/tests/repl-model.test.ts @@ -496,7 +496,7 @@ function withPrompt( describe("REPL model: the Agent conversations it projects", () => { beforeAll(() => useTempFileCompiler()); - it("M1: projects each retained Prompt as one turn owned by its exact scope", function* () { + it("M1: projects each saved Prompt as one turn owned by its exact scope", function* () { const events = yield* agentReferenceEvents(); const model = projected(events); @@ -514,7 +514,7 @@ describe("REPL model: the Agent conversations it projects", () => { "planner", ]); // Every turn belongs to the one scope whose source it was written in, at - // the exact position the record retained. + // the exact position the record saved. expect(model.turns.map((turn) => turn.scope)).toEqual([ ENTRY_SCOPE, ENTRY_SCOPE, @@ -633,7 +633,7 @@ describe("REPL model: the Agent conversations it projects", () => { } }); - it("M3: one retained turn is one object, however it is reached", function* () { + it("M3: one saved turn is one object, however it is reached", function* () { const events = yield* agentReferenceEvents(); const model = projected(events); const built = model.turns[2]; @@ -888,14 +888,14 @@ function planJournal( } describe("REPL model: the declared sources it owns", () => { - it("M3: a declared component's retained origin and bytes become its scope", function* () { + it("M3: a declared component's saved origin and bytes become its scope", function* () { const model = projected(planJournal()); const entry = only(model); if (entry === undefined) { throw new Error("the journal admits one entry"); } - // The scope the import created, under the origin the record retained — the + // The scope the import created, under the origin the record saved — the // path every effect inside that component is recorded at — holding the exact // bytes the record carried. const plan = child(entry, "Plan-1"); diff --git a/packages/cli/tests/repl-presentation-style.test.ts b/packages/cli/tests/repl-presentation-style.test.ts index caa4e2c6a..99b08c565 100644 --- a/packages/cli/tests/repl-presentation-style.test.ts +++ b/packages/cli/tests/repl-presentation-style.test.ts @@ -25,7 +25,7 @@ import { collect, Elicitation, inlineSource, - retainedSource, + savedSource, useTempFileCompiler, } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; @@ -67,7 +67,7 @@ import { BOLD, REPL_PALETTE } from "../src/repl/presentation-style.ts"; import { REFERENCE_ARCHIVE, REFERENCE_BOLD, - RETAINED, + SAVED, SEMANTIC, SURFACE, SYNTAX, @@ -209,10 +209,7 @@ function* runEntry( function* runHostEntry(physical: DurableStream, path: string, source: string): Operation { const view = new EntrySegmentStream(physical); try { - const execution = yield* executeInstalled( - { ...retainedSource(path, source), stream: view }, - [], - ); + const execution = yield* executeInstalled({ ...savedSource(path, source), stream: view }, []); yield* collect(execution); } catch { // As above: how far it got is the record the catalog reads. @@ -378,7 +375,7 @@ function reading( focused?: string, ): ReplView { // The navigation a session would publish for this reading: every position - // the file retained, with the head it is standing at. Derived from the + // the file saved, with the head it is standing at. Derived from the // model here because these cases build the model directly rather than // running a session. const resolved = viewFor( @@ -450,7 +447,7 @@ function within(inner: ReplBounds, outer: ReplBounds): boolean { * Before #881 PR 2 this was a transcript record row — a fact about the event * that produced the output beside it. The transcript now holds the entry's * reading, so the subordinate reading is taken from a row that still carries - * it: what the frame says where it has nothing retained to list. + * it: what the frame says where it has nothing saved to list. */ function metadataKey(observed: Observed): string { const key = observed.keys.find((one) => one === "sessions:empty"); @@ -600,7 +597,7 @@ describe("REPL presentation: what one row's cells say it is", () => { // row is. expect(inkOfSpan(grid, first, said).foreground).toBe(REPL_PALETTE.output); expect(inkOfSpan(grid, first, said).foreground).not.toBe(REPL_PALETTE.success); - // And it is labelled as the retained result rather than as live text. + // And it is labelled as the saved result rather than as live text. expect(textOf(grid, placed(observed, "reading:caption:output"))).toContain("Output"); expect(textOf(grid, placed(observed, "reading:caption:output"))).not.toContain("live"); @@ -1165,11 +1162,11 @@ describe("REPL presentation: what a drawer says it is showing", () => { it("D1: a recorded answer says so, and keeps the origin it came from", function* () { // The reference entry, which asks a question and is answered — so this - // history really holds a retained answer to inspect. + // history really holds a saved answer to inspect. const model = projected(yield* referenceEvents()); const recorded = model.entries.flatMap((entry) => entry.scope.elicitations)[0]; if (recorded === undefined) { - throw new Error("this history retained no answer to inspect"); + throw new Error("this history saved no answer to inspect"); } const presenter = yield* usePresenter(WIDE); const state = openingDrawer(selecting(stateWith({}), "entry-1"), { @@ -1233,7 +1230,7 @@ describe("REPL presentation: what a drawer says it is showing", () => { expect(textOf(grid, placed(observed, "drawer:open")).trim()).toBe("History"); const marker = model.checkpoints[0]; if (marker === undefined) { - throw new Error("this history retained no position"); + throw new Error("this history saved no position"); } const row = placed(observed, `drawer:marker:${marker.marker}`); expect(inkOf(grid, row).foreground).toBe(REPL_PALETTE.historical); @@ -1266,7 +1263,7 @@ describe("REPL presentation: what a drawer says it is showing", () => { const model = projected(events); const marker = model.checkpoints[0]; if (marker === undefined) { - throw new Error("this history retained no position"); + throw new Error("this history saved no position"); } const inspected = yield* scoped(function* (): Operation { @@ -1718,7 +1715,7 @@ describe("REPL presentation: focus marks the row without repainting it", () => { // the cue that survives colour being thrown away. const selected = placed(observed, "entry:entry-1"); for (let x = selected.x; x < selected.x + selected.width; x += 1) { - expect(grid.styleAt(x, selected.y).background).toBe(RETAINED.selectedSurface); + expect(grid.styleAt(x, selected.y).background).toBe(SAVED.selectedSurface); } expect(textOf(grid, selected)).toContain("*"); expect(inkOf(grid, selected).foreground).toBe(SEMANTIC.tick); @@ -2080,7 +2077,7 @@ describe("REPL presentation: the surfaces a reading is drawn on", () => { // Every cell of the rectangle is the drawer's, the unused ones included. const own: readonly number[] = [ SURFACE.drawer, - RETAINED.selectedSurface, + SAVED.selectedSurface, SURFACE.field, SURFACE.input, ]; diff --git a/packages/cli/tests/repl-repositories.test.ts b/packages/cli/tests/repl-repositories.test.ts index 4cff74de6..37c011213 100644 --- a/packages/cli/tests/repl-repositories.test.ts +++ b/packages/cli/tests/repl-repositories.test.ts @@ -8,12 +8,12 @@ * does that work rather than answering from the earlier entry's records. A view * that is only reading — a settled history, a frozen position — prepares * nothing at all, so it discovers nothing and takes no lease. And a host that - * installs no setup runs ordinary file work with nothing retained. + * installs no setup runs ordinary file work with nothing saved. * * The generated half is here rather than beside the provider because a * generated fragment's operations cross the same one Files route an authored * one does: there is no second instance to wrap, and what proves it is a - * fragment's read, write and delete being retained exactly as an authored + * fragment's read, write and delete being saved exactly as an authored * one's are. */ @@ -35,7 +35,7 @@ import { parseReplLocalRecord, REPL_REPOSITORY_LOCAL, REPL_REPOSITORY_SELECTION, -} from "../../git/src/retained-repository/records.ts"; +} from "../../git/src/saved-repository/records.ts"; import { Execution } from "@executablemd/core/api"; import type { ExecutionInstallation } from "@executablemd/core/host"; import { ordinaryGeneratedSelection } from "../src/evaluation-profile.ts"; @@ -203,7 +203,7 @@ function* submit( yield* useWorkingDirectory(fixture.checkout); const installations = yield* replInstallations(); // Absent for the row about a host that registers none: the entry then runs - // on the ordinary Files provider with nothing retained. + // on the ordinary Files provider with nothing saved. if (counted !== undefined) { yield* useCountedRecording(counted); } @@ -514,7 +514,7 @@ describe("how a REPL entry gets its repository behavior", () => { // A record of this feature's type that this version never wrote, standing // *past* where the entry stopped. Nothing replays it, so the operation-level // reader never looks at it: what refuses it is the admission the Git Plugin - // contributes for this command, which reads the whole retained segment + // contributes for this command, which reads the whole saved segment // before any of it is replayed. const tampered: readonly DurableEvent[] = [ ...events, diff --git a/packages/cli/tests/repl-route.test.ts b/packages/cli/tests/repl-route.test.ts index e29b2182a..9df4872f0 100644 --- a/packages/cli/tests/repl-route.test.ts +++ b/packages/cli/tests/repl-route.test.ts @@ -453,7 +453,7 @@ describe("REPL route: resolving against one model", () => { * Two halves again. The grammar has to spell one more query member without * moving any of the ones that were canonical before it, and has to keep the new * drawer on the one surface that has it. Resolution has to answer a filter from - * what the *selected prefix* retained, plus — only at the live head — what the + * what the *selected prefix* saved, plus — only at the live head — what the * process says it has started, and has to refuse anything else rather than * showing an empty list. */ @@ -490,7 +490,7 @@ describe("REPL route: a live question's drawer belongs to one place", () => { ).toContain("frozen at an earlier position"); // Cold, with no process holding a question: a Journal records answers, never a - // question that is still waiting, so nothing retained can establish this. + // question that is still waiting, so nothing saved can establish this. expect(unresolved(model, `xmd://repl/${EXECUTION}/entries/entry-1/+elicit`)).toContain( "nothing is being asked", ); @@ -590,7 +590,7 @@ describe("REPL route: the conversation filter", () => { describe("REPL route: resolving a conversation", () => { beforeAll(() => useTempFileCompiler()); - it("R2: resolves a retained conversation to the exact group the model holds", function* () { + it("R2: resolves a saved conversation to the exact group the model holds", function* () { const events = yield* agentReferenceEvents(); const model = projected(events); @@ -610,7 +610,7 @@ describe("REPL route: resolving a conversation", () => { const started = `xmd://repl/${EXECUTION}/sessions?session=stub%3Aplanning`; // Nothing has settled in it yet, so no history holds it and there is no - // retained group to point at — but the process running it can say it exists. + // saved group to point at — but the process running it can say it exists. expect(unresolved(model, started)).toContain("no conversation stub:planning"); const live = resolved(model, started, holding({ sessions: ["stub:planning"] })); expect(live.session).toBe(undefined); @@ -622,17 +622,17 @@ describe("REPL route: resolving a conversation", () => { ); }); - it("R2: a historical prefix answers from its own retained groups and ignores the head", function* () { + it("R2: a historical prefix answers from its own saved groups and ignores the head", function* () { const events = yield* agentReferenceEvents(); const head = projected(events); const early = head.turns[0].marker; const historical = projected(events, early); - const retained = resolved( + const saved = resolved( historical, `xmd://repl/${EXECUTION}/sessions?at=${early}&session=stub%3Areview`, ); - expect(retained.session).toBe(historical.sessions[0]); + expect(saved.session).toBe(historical.sessions[0]); // The build conversation had not started at this position. It is in the // head and in this process, and neither may answer for the past. diff --git a/packages/cli/tests/repl-source-reading.test.ts b/packages/cli/tests/repl-source-reading.test.ts index 92c6e7824..8443b409d 100644 --- a/packages/cli/tests/repl-source-reading.test.ts +++ b/packages/cli/tests/repl-source-reading.test.ts @@ -531,7 +531,7 @@ describe("the reading one entry is shown as", () => { expect(reading.output.lines.map((one) => one.text)).toEqual(["partial outp"]); }); - it("replaces it with the retained result, once, unlabelled", function* () { + it("replaces it with the saved result, once, unlabelled", function* () { const reading = entryReading({ entry: entryOf("hello\n", { terminal: settled("ok", "hello"), settled: true }), lifecycle: NO_LIFECYCLE, diff --git a/packages/cli/tests/service-document.test.ts b/packages/cli/tests/service-document.test.ts index 86bc3720b..a46de2f08 100644 --- a/packages/cli/tests/service-document.test.ts +++ b/packages/cli/tests/service-document.test.ts @@ -422,7 +422,7 @@ yield* Sample.around({ yield* expectGone(fixturePids(stderr)); }); - it("fails projected content promptly, tears down retained service attachments, and does not restart", function* () { + it("fails projected content promptly, tears down saved service attachments, and does not restart", function* () { const stderr: string[] = []; let tokenCalls = 0; yield* scoped(function* () { @@ -445,11 +445,11 @@ meta: --- \`\`\`bash service=firstServer exec -${command("normal", "first-retained")} +${command("normal", "first-saved")} \`\`\` \`\`\`bash service=secondServer exec -${command("normal", "second-retained")} +${command("normal", "second-saved")} \`\`\` @@ -485,8 +485,8 @@ throw new Error("projected content failure"); const pids = fixturePids(stderr); expect(pids).toHaveLength(2); - expect(stderr.join("")).toContain("service stopping:first-retained"); - expect(stderr.join("")).toContain("service stopping:second-retained"); + expect(stderr.join("")).toContain("service stopping:first-saved"); + expect(stderr.join("")).toContain("service stopping:second-saved"); yield* expectGone(pids); }); diff --git a/packages/cli/tests/support/pty.ts b/packages/cli/tests/support/pty.ts index 36c4fb289..cacca85bb 100644 --- a/packages/cli/tests/support/pty.ts +++ b/packages/cli/tests/support/pty.ts @@ -4,7 +4,7 @@ * Copied rather than imported from `repl-exit-pty.test.ts`, which is where it * was written and which still owns its own copy. Two suites need a real * terminal for two different promises — one about leaving the screen, one about - * what an entry retained in a checkout — and neither is a reason to move the + * what an entry saved in a checkout — and neither is a reason to move the * other's evidence. The duplication is deliberate and bounded: it is this file * and that one, and nothing else reaches for a pty. * diff --git a/packages/cli/tests/support/repl-files-journey.ts b/packages/cli/tests/support/repl-files-journey.ts index 13611ced8..488b79500 100644 --- a/packages/cli/tests/support/repl-files-journey.ts +++ b/packages/cli/tests/support/repl-files-journey.ts @@ -272,7 +272,7 @@ export function replFilesJourney(repl: ReplCommand): Operation { expect(yield* ended(second)).toBe(0); // A separate new entry, opened at the same location: its read is new work, - // so it reads the file as it is now. What is retained is each operation, + // so it reads the file as it is now. What is saved is each operation, // not a value the session remembers for the path. // // A third session rather than another entry typed into the second one: a diff --git a/packages/cli/tests/support/workflow-run.ts b/packages/cli/tests/support/workflow-run.ts index 50821161d..3bb7b64d0 100644 --- a/packages/cli/tests/support/workflow-run.ts +++ b/packages/cli/tests/support/workflow-run.ts @@ -34,7 +34,7 @@ export function withStorage(root: string, body: () => Operation): Operatio } /** - * One run of one retained document, created the way a host creates one. + * One run of one saved document, created the way a host creates one. * * The descriptor is derived from the bytes rather than written beside them, so * the bundle hash this retains is the hash those bytes really produce — which diff --git a/packages/cli/tests/testing-activation.test.ts b/packages/cli/tests/testing-activation.test.ts index aecda746a..8faf704c9 100644 --- a/packages/cli/tests/testing-activation.test.ts +++ b/packages/cli/tests/testing-activation.test.ts @@ -2,7 +2,7 @@ * A real workflow installation grants no testing capability. * * `executeInstalled()` is how a trusted host attaches what an execution needs, - * and a retained workflow installation is the most privileged thing this + * and a saved workflow installation is the most privileged thing this * repository attaches through it. It decides the run, the definition and the * journal — none of which is a testing session — so a document that registered * the testing components and answered the public boolean `true` is in exactly @@ -19,7 +19,7 @@ import type { DurableEvent } from "@executablemd/durable-streams"; import { inlineSource, registerComponents, useTempFileCompiler } from "@executablemd/core"; import type { Json } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; -import { retainedWorkflowInstallation } from "@executablemd/workflow"; +import { savedWorkflowInstallation } from "@executablemd/workflow"; import { GitQuery } from "@executablemd/git/api"; import { installTestingComponents } from "@executablemd/testing"; import type { TestResult } from "@executablemd/testing"; @@ -29,7 +29,7 @@ const RUN = Object.freeze({ bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", }); -/** A Git that fails the run if the retained installation consults it. */ +/** A Git that fails the run if the saved installation consults it. */ function useForbiddenGit(): Operation { return GitQuery.around( { @@ -90,7 +90,7 @@ describe("workflow installation and testing activation", () => { ...inlineSource('\n'), stream, }, - [retainedWorkflowInstallation(RUN)], + [savedWorkflowInstallation(RUN)], ); return yield* execution; }); diff --git a/packages/cli/tests/testing-execution-host.test.ts b/packages/cli/tests/testing-execution-host.test.ts index f9d8175a5..0995f439d 100644 --- a/packages/cli/tests/testing-execution-host.test.ts +++ b/packages/cli/tests/testing-execution-host.test.ts @@ -420,7 +420,7 @@ describe("nested execution under the production run host", () => { */ it("gives a run child the ordinary read profile: File, Glob and Syntax", function* () { const project = yield* useProject({ - "notes.md": doc("the retained note"), + "notes.md": doc("the saved note"), "reader.md": doc( `\\n` + `\\n` + @@ -438,7 +438,7 @@ describe("nested execution under the production run host", () => { '', "", '', - '', + '', "", "", "", @@ -500,7 +500,7 @@ const SCHEMA = * A behavior document with a third stage nothing sends. * * `unsentstagemarker` is in the scenario source and in no prompt and no reply, - * so a journal that mentions it retained configuration rather than results. + * so a journal that mentions it saved configuration rather than results. */ const BEHAVIOR = doc( '', @@ -581,7 +581,7 @@ describe("deterministic dependencies declared for a nested run", () => { '', '', "", - // What the child actually did is retained; what configured it is not. + // What the child actually did is saved; what configured it is not. '', '', '', diff --git a/packages/cli/tests/workflow-agent-sessions.test.ts b/packages/cli/tests/workflow-agent-sessions.test.ts index 8b71066f2..e0835de28 100644 --- a/packages/cli/tests/workflow-agent-sessions.test.ts +++ b/packages/cli/tests/workflow-agent-sessions.test.ts @@ -21,7 +21,7 @@ import { readTextFile } from "@effectionx/fs"; import { DatabaseSync } from "node:sqlite"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { agentIdentityComponents, collect, retainedSource } from "@executablemd/core"; +import { agentIdentityComponents, collect, savedSource } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import type { Json } from "@executablemd/durable-streams"; import { API, useHostFiles } from "@executablemd/runtime"; @@ -81,7 +81,7 @@ function attach( return yield* collect( yield* executeInstalled( { - ...retainedSource("workflows/sibling-sessions.md", source), + ...savedSource("workflows/sibling-sessions.md", source), stream: database.journal, }, // `` names durable work after its own invocation, so the @@ -115,7 +115,7 @@ function established(fake: FakeAcp): string[] { return [...new Set(fake.ensured.map((input) => input.sessionKey))].sort(); } -/** One retained mapping, as a second connection sees it. */ +/** One saved mapping, as a second connection sees it. */ interface MappingRow { readonly sessionIdentity: string; readonly kind: string; @@ -272,7 +272,7 @@ describe("Tier WSR — a restart reattaches each Session site", () => { * substituted. */ describe("Tier WSA — the workflow profile serves Claude over ACP alone", () => { - it("WSR3: a Claude session is retained and reattached with no machine-wide account", function* () { + it("WSR3: a Claude session is saved and reattached with no machine-wide account", function* () { const root = yield* useStorageRoot(); const source = yield* documentSource("claude-session.md"); diff --git a/packages/cli/tests/workflow-agent.test.ts b/packages/cli/tests/workflow-agent.test.ts index d1479e780..a2f93dd67 100644 --- a/packages/cli/tests/workflow-agent.test.ts +++ b/packages/cli/tests/workflow-agent.test.ts @@ -21,7 +21,7 @@ import type { Operation } from "effection"; import { ensureDir, exists, readTextFile } from "@effectionx/fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { collect, execute, retainedSource } from "@executablemd/core"; +import { collect, execute, savedSource } from "@executablemd/core"; import type { Json } from "@executablemd/durable-streams"; import type { RuntimeFetchResponse } from "@executablemd/runtime"; import type { DurableEvent } from "@executablemd/durable-streams"; @@ -51,7 +51,7 @@ const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "fixtures", "work const NOTE = "the release checklist is three items long\n"; /** - * What ACPX resolves this agent to, and what the base branch retained for it. + * What ACPX resolves this agent to, and what the base branch saved for it. * * Written out rather than read back through `createAgentRegistry()`, because * the requirement is that this value did not move. Recomputing it from the same @@ -116,7 +116,7 @@ function stubAdapters(): EmbeddedAdapters & { readonly materialized: string[] } return { materialized, providers: ["codex", "claude"], - // Stable, and deliberately carrying no path: this is what a retained + // Stable, and deliberately carrying no path: this is what a saved // session and a sealed artifact record, so it must not vary with where a // host materialized anything. identity(provider) { @@ -136,8 +136,8 @@ function stubAdapters(): EmbeddedAdapters & { readonly materialized: string[] } }; } -/** Every Agent session one run retained, as its database holds it. */ -function retainedSessions(path: string): Record[] { +/** Every Agent session one run saved, as its database holds it. */ +function savedSessions(path: string): Record[] { const database = new DatabaseSync(path); try { return database.prepare("SELECT * FROM agent_sessions ORDER BY session_key").all(); @@ -152,8 +152,8 @@ const TOKEN_VALUE = "wal-canary-turn-6b3f9d"; /** The kind this host records that identity under. */ const TOKEN_KIND = "app-server-turn-id"; -/** Which provider turn each Prompt of this run was, as the run retained it. */ -function* retained( +/** Which provider turn each Prompt of this run was, as the run saved it. */ +function* saved( database: WorkflowRunDatabase, ): Operation<{ eventId: string; tokenKind: string; tokenValue: string }[]> { const read = yield* transactAgentPromptCheckpoints(database, function* (checkpoints) { @@ -262,7 +262,7 @@ function runFixture( return yield* collect( yield* executeInstalled( { - ...retainedSource("workflows/observation-loop.md", source), + ...savedSource("workflows/observation-loop.md", source), stream: database.journal, ...(options.props === undefined ? {} : { props: options.props }), }, @@ -505,7 +505,7 @@ describe("Tier WAL — the workflow Agent observation loop", () => { }); }); - it("WAL10: a named turn is retained against its Prompt and rendered nowhere", function* () { + it("WAL10: a named turn is saved against its Prompt and rendered nowhere", function* () { const root = yield* useStorageRoot(); const source = yield* documentWithNote(); @@ -518,9 +518,9 @@ describe("Tier WAL — the workflow Agent observation loop", () => { const attempt = yield* runFixture(root, database, source, { createRuntime: fake.create }); expect(attempt.failure).toBe(undefined); - // Retained, and against the run's own Prompt events. Without this the + // Saved, and against the run's own Prompt events. Without this the // absence below would be the absence of something that never existed. - const rows = yield* retained(database); + const rows = yield* saved(database); expect(rows.map((row) => row.tokenValue)).toEqual([`${TOKEN_VALUE}-1`, `${TOKEN_VALUE}-2`]); expect(rows.every((row) => row.tokenKind === TOKEN_KIND)).toBe(true); const promptEvents = typed(attempt.events, "agent_prompt").map((event) => event); @@ -567,7 +567,7 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // that rendering is what reaches the next prompt. expect(fake.prompts).toHaveLength(2); const second = fake.prompts[1] ?? ""; - // The complete retained response, not a summary of it: status, headers and + // The complete saved response, not a summary of it: status, headers and // body, exactly as the fragment chose to render them. expect(second).toContain('"status": 200'); expect(second).toContain('"content-type"'); @@ -611,7 +611,7 @@ describe("Tier WAL — the workflow Agent observation loop", () => { expect(observed).toContain("the release notes are ready"); // The same document over its own journal: the response is restored from - // the retained `fetch` effect, and neither the agent nor the server is + // the saved `fetch` effect, and neither the agent nor the server is // asked anything. const replayed: string[] = []; const reached: string[] = []; @@ -648,14 +648,14 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // observation where a proposal was required. expect(reported(attempt)).toMatch(/proposal|const/i); expect(fake.prompts).toHaveLength(4); - // And the evidence that produced it is retained: four turns, four + // And the evidence that produced it is saved: four turns, four // admissions. expect(typed(attempt.events, "agent_prompt")).toHaveLength(4); expect(admitted(attempt.events)).toHaveLength(4); }); }); - it("WAL5: a mutation-shaped proposal is retained as data and mutates nothing", function* () { + it("WAL5: a mutation-shaped proposal is saved as data and mutates nothing", function* () { const root = yield* useStorageRoot(); const source = yield* documentWithNote(); @@ -679,7 +679,7 @@ describe("Tier WAL — the workflow Agent observation loop", () => { }); }); - it("WAL6: a denied native tool becomes the retained Prompt failure", function* () { + it("WAL6: a denied native tool becomes the saved Prompt failure", function* () { const root = yield* useStorageRoot(); const source = yield* documentWithNote(); @@ -696,13 +696,13 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // Rejected where ACP offered a rejection. expect(fake.decisions).toEqual(["reject_once"]); - // The turn failed, and the failure is retained rather than inferred. + // The turn failed, and the failure is saved rather than inferred. const prompts = typed(attempt.events, "agent_prompt"); expect(prompts).toHaveLength(1); - const retained = JSON.stringify(prompts[0]); - expect(retained).toContain("native tool"); + const saved = JSON.stringify(prompts[0]); + expect(saved).toContain("native tool"); // Nothing the request carried reached the record. - expect(retained).not.toContain(marker); + expect(saved).not.toContain(marker); }); }); @@ -817,7 +817,7 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // Read straight out of the file, because the durable row is the claim // — it is what a later attachment compares and what the artifact // encoder writes `agentCommand` from. - sessions: retainedSessions(workflowRunPath(root, "observation-run")), + sessions: savedSessions(workflowRunPath(root, "observation-run")), launch: adapters.executablePath("codex"), identity: adapters.identity("codex"), }; @@ -849,11 +849,11 @@ describe("Tier WAL — the workflow Agent observation loop", () => { expect(here.sessions[0]?.["agent_command"]).toBe(here.identity); expect(there.sessions[0]?.["agent_command"]).toBe(here.identity); - // Neither host's path is anywhere in what was retained. - const retained = JSON.stringify(here.sessions) + JSON.stringify(there.sessions); - expect(retained).not.toContain(plainRoot); - expect(retained).not.toContain(spacedRoot); - expect(retained).not.toContain("adapters/"); + // Neither host's path is anywhere in what was saved. + const saved = JSON.stringify(here.sessions) + JSON.stringify(there.sessions); + expect(saved).not.toContain(plainRoot); + expect(saved).not.toContain(spacedRoot); + expect(saved).not.toContain("adapters/"); // And each host still launches its own local executable. expect(there.launch).not.toBe(here.launch); @@ -894,13 +894,13 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // provider it does not carry, so any of those would have failed the run. expect(adapters.materialized).toEqual([]); - // The ordinary Prompt events are retained exactly as they always were. + // The ordinary Prompt events are saved exactly as they always were. expect(typed(attempt.events, "agent_prompt")).toHaveLength(2); // And nothing was associated, because nothing named a turn. An absent // checkpoint is not a failed Prompt: the event above is the proof that // this zero is a zero rather than a run that did not happen. - expect(yield* retained(database)).toEqual([]); + expect(yield* saved(database)).toEqual([]); }); }); @@ -934,9 +934,9 @@ describe("Tier WAL — the workflow Agent observation loop", () => { expect(reported(replay)).toBe(reported(first)); expect(reached).toEqual([]); expect(replay.events.length).toBe(committed); - // And still nothing associated. A run that retained no checkpoint + // And still nothing associated. A run that saved no checkpoint // replays from the stored Prompt alone, exactly like one that did. - expect(yield* retained(database)).toEqual([]); + expect(yield* saved(database)).toEqual([]); }); // An interrupted run, resumed. This is the partial case: one Prompt is on @@ -988,11 +988,11 @@ describe("Tier WAL — the workflow Agent observation loop", () => { ); // Two completed Prompts on the journal and no association for either. expect(typed(second.events, "agent_prompt")).toHaveLength(2); - expect(yield* retained(database)).toEqual([]); + expect(yield* saved(database)).toEqual([]); }); }); - it("WAL15: its retained identity is the resolved command, as it was before", function* () { + it("WAL15: its saved identity is the resolved command, as it was before", function* () { const root = yield* useStorageRoot(); const source = yield* baselineAgentSource(); @@ -1007,14 +1007,14 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // The compatibility attribute a reattachment compares and an artifact // seals. For an agent this build does not override it is ACPX's resolved - // command and nothing else — the same value this profile retained before - // any adapter was embedded, so a session retained by an earlier run of + // command and nothing else — the same value this profile saved before + // any adapter was embedded, so a session saved by an earlier run of // the same workflow is still that session. - const rows = retainedSessions(workflowRunPath(root, "observation-run")); + const rows = savedSessions(workflowRunPath(root, "observation-run")); expect(rows).toHaveLength(1); - // Two claims, and they are different ones. That the retained value *is* + // Two claims, and they are different ones. That the saved value *is* // what the registry resolved, and that what the registry resolved is - // ACPX's own built-in — the literal the base branch retained for this + // ACPX's own built-in — the literal the base branch saved for this // agent, written out rather than recomputed through the code under test. expect(rows[0]?.["agent_command"]).toBe(fake.created[0]?.agentRegistry.resolve("gemini")); expect(rows[0]?.["agent_command"]).toBe(GEMINI_COMMAND); @@ -1071,10 +1071,10 @@ describe("Tier WAL — the workflow Agent observation loop", () => { // Refused, by the snapshot's own words, and before any turn: the run // never sent a Prompt and never fell through to the published Codex - // adapter, which would have completed everything and retained nothing. + // adapter, which would have completed everything and saved nothing. expect(attempt.failure).toContain("cannot be verified"); expect(fake.prompts).toEqual([]); - expect(yield* retained(database)).toEqual([]); + expect(yield* saved(database)).toEqual([]); }); // The same broken snapshots, and an agent they have nothing to do with. diff --git a/packages/cli/tests/workflow-cli.test.ts b/packages/cli/tests/workflow-cli.test.ts index f48c4555f..9d2d93d7a 100644 --- a/packages/cli/tests/workflow-cli.test.ts +++ b/packages/cli/tests/workflow-cli.test.ts @@ -322,7 +322,7 @@ describe("Tier WFC — xmd workflow start and resume", () => { expect(failed.stderr).toContain("ComponentBesideTheDefinition"); expect(failed.stderr).toContain("searched: "); - // A compatible reuse of retained failed history replays that failure + // A compatible reuse of saved failed history replays that failure // rather than retrying the work. const replayed = yield* xmd(fixture, [ "workflow", @@ -463,7 +463,7 @@ describe("Tier WFC — xmd workflow start and resume", () => { expect(refused.stderr).toContain(option.split("=")[0] ?? option); } - // A delivery writes retained state, so the secret gate is one of its own. + // A delivery writes saved state, so the secret gate is one of its own. const optedOut = yield* xmd(fixture, [ "workflow", "answer", @@ -629,7 +629,7 @@ describe("Tier WFC — xmd workflow start and resume", () => { expect(status.stdout).not.toContain("Pub*"); // The document is taken away, and the resume still runs that section out - // of what the run retained rather than resolving the selector again. + // of what the run saved rather than resolving the selector again. yield* rm(join(fixture.repository, "flows/sections.md"), { force: true }); const resumed = yield* xmd(fixture, ["workflow", "resume", "section-1"]).join(); expect(resumed.code).toBe(0); @@ -737,7 +737,7 @@ describe("Tier WFC — a workflow closed over a component bundle", () => { yield* useFixture(LOOP_FILES, function* (fixture) { // Every one of the six files says something else in the working tree by // the time the run starts, and that is what the run is of: the bundle is - // read from the directory the root lives in, and retained with it. + // read from the directory the root lives in, and saved with it. yield* editCheckout(fixture); const started = yield* xmd(fixture, ["workflow", "start", "flows/loop.md"]).join(); @@ -881,7 +881,7 @@ describe("Tier WFC — a workflow closed over a component bundle", () => { expect(reportedStatus(resumed.stderr)).toBe("completed"); expect(resumed.stdout).toContain("implemented."); - // A completed run replays what it retained: the bundle was reconstructed + // A completed run replays what it saved: the bundle was reconstructed // and the history admitted, and then nothing was performed or appended. const after = yield* xmd(fixture, ["workflow", "history", "loop-3", "--json"]).join(); expect(after.stdout).toBe(before.stdout); @@ -1113,7 +1113,7 @@ describe("Tier WFX — what xmd workflow export refuses", () => { }); }); - it("WFX6: refuses when the run's own retained source no longer describes itself", function* () { + it("WFX6: refuses when the run's own saved source no longer describes itself", function* () { yield* useFixture({ "flows/release.md": RELEASE }, function* (fixture) { const started = yield* xmd(fixture, [ "workflow", diff --git a/packages/cli/tests/workflow-crash.test.ts b/packages/cli/tests/workflow-crash.test.ts index 53ffbe18d..e961c7140 100644 --- a/packages/cli/tests/workflow-crash.test.ts +++ b/packages/cli/tests/workflow-crash.test.ts @@ -2,7 +2,7 @@ * Tier WFX — what a killed `xmd workflow start` leaves, and what a resume does * with it. * - * The point of a retained workflow is that losing the host is survivable, so + * The point of a saved workflow is that losing the host is survivable, so * this is made of real processes and a real `SIGKILL`. Nothing runs after the * signal — no cleanup, no commit, no rollback — and what the next process finds * is whatever the last committed transaction left. @@ -161,7 +161,7 @@ function committedEffects(path: string): Operation { }); } -/** The run's retained status, as a second connection sees it. */ +/** The run's saved status, as a second connection sees it. */ function committedStatus(path: string): Operation { return readRunDatabase(path, (database) => { const row = database.prepare("SELECT status FROM workflow_run WHERE id = 1").get(); diff --git a/packages/cli/tests/workflow-declaration.test.ts b/packages/cli/tests/workflow-declaration.test.ts index e792b4ba8..71b82dc6c 100644 --- a/packages/cli/tests/workflow-declaration.test.ts +++ b/packages/cli/tests/workflow-declaration.test.ts @@ -303,10 +303,10 @@ describe("Tier WFD — establishing a declared bundle", () => { expect(definition.components).toEqual([{ name: "Stage", path: "stages/Stage.md" }]); expect(sourceSnapshot.map((entry) => entry.path)).toEqual(["root.md", "stages/Stage.md"]); - // The bytes retained for the component are the component's, and the + // The bytes saved for the component are the component's, and the // execution view carries the same identity the descriptor does. - const retained = sourceSnapshot.find((entry) => entry.path === "stages/Stage.md"); - expect(new TextDecoder().decode(retained?.bytes)).toBe(stage); + const saved = sourceSnapshot.find((entry) => entry.path === "stages/Stage.md"); + expect(new TextDecoder().decode(saved?.bytes)).toBe(stage); expect(components.map((component) => component.name)).toEqual(["Stage"]); expect(components[0]?.content).toBe(stage); expect(components[0]?.sourceHash).toBe( @@ -354,7 +354,7 @@ describe("Tier WFD — establishing a declared bundle", () => { * The argument is a document reference, so `notes.md#Release/Publish` names a * section rather than a file whose name ends in `#Release/Publish`. What the * descriptor keeps is the canonical target core resolved against the bytes that - * are about to be retained — never the selector the caller wrote, because two + * are about to be saved — never the selector the caller wrote, because two * spellings of one request are one run and a glob re-resolved against different * bytes would name a different section. */ @@ -418,13 +418,13 @@ describe("Tier WFD — the target a reference resolves", () => { if (!result.ok) { throw result.error; } - // What is retained is what core resolved, so a resume continues the + // What is saved is what core resolved, so a resume continues the // section this start selected rather than re-answering the pattern. expect(result.value.definition.targetPath).toBe("Publish"); }); }); - it("WFD46: a selector naming no section refuses, before anything is retained", function* () { + it("WFD46: a selector naming no section refuses, before anything is saved", function* () { yield* useDocument({ "sections.md": SECTIONS }, function* (directory) { const absent = yield* establishDefinition(`${join(directory, "sections.md")}#Nowhere`); expect(absent.ok).toBe(false); @@ -442,7 +442,7 @@ describe("Tier WFD — the target a reference resolves", () => { const escaped = yield* establishDefinition(`${join(directory, "od%23d.md")}`); expect(escaped.ok).toBe(false); // Refused for what it is: a logical path inside a bundle admits no `#`, - // because a retained path holding one could not be told from a path and + // because a saved path holding one could not be told from a path and // a target. The refusal names the grammar, not the selector parser. expect(escaped.ok ? "" : escaped.error.message).toContain('without a "#"'); diff --git a/packages/cli/tests/workflow-export-publication.test.ts b/packages/cli/tests/workflow-export-publication.test.ts index 67602163a..d19dc57e5 100644 --- a/packages/cli/tests/workflow-export-publication.test.ts +++ b/packages/cli/tests/workflow-export-publication.test.ts @@ -115,7 +115,7 @@ function useExportedRun(body: (fixture: Fixture) => Operation): Operation< /** * The host this package installs, pointed at the fixture's runs. * - * Run from inside the repository, because reading a retained definition back + * Run from inside the repository, because reading a saved definition back * means opening the checkout that run recorded. */ function withHost(fixture: Fixture, body: () => Operation): Operation { @@ -376,9 +376,9 @@ describe("Tier WXP — what an export leaves behind", () => { expect(said.err.join("\n")).not.toContain("will not replace"); // The warning names the private entry that was left, and only that. - const retained = yield* leftovers(fixture); - expect(retained).toHaveLength(1); - expect(said.err.join("\n")).toContain(retained[0] ?? ""); + const saved = yield* leftovers(fixture); + expect(saved).toHaveLength(1); + expect(said.err.join("\n")).toContain(saved[0] ?? ""); expect(said.err.join("\n")).toContain("The artifact is complete"); // The committed output is never a target of a removal — not by the diff --git a/packages/cli/tests/workflow-fetch.test.ts b/packages/cli/tests/workflow-fetch.test.ts index 2218f0338..589016779 100644 --- a/packages/cli/tests/workflow-fetch.test.ts +++ b/packages/cli/tests/workflow-fetch.test.ts @@ -244,17 +244,17 @@ describe("Tier FT — a workflow run's Fetch", () => { method: "GET", headers: { accept: "application/json" }, }); - const retained = committed?.result?.value; - if (retained === null || typeof retained !== "object") { + const saved = committed?.result?.value; + if (saved === null || typeof saved !== "object") { throw new Error("the run committed no Fetch response"); } - const value: Record = { ...retained }; + const value: Record = { ...saved }; expect(Object.keys(value)).toEqual(["status", "headers", "body"]); expect(value.status).toBe(200); expect(value.body).toBe('{"greeting":"hello"}'); const headers = value.headers; if (headers === null || typeof headers !== "object") { - throw new Error("the retained response carries no header object"); + throw new Error("the saved response carries no header object"); } // Whatever the host reported, the names are lowercase and ordered. const named: Record = { ...headers }; diff --git a/packages/cli/tests/workflow-fork.test.ts b/packages/cli/tests/workflow-fork.test.ts index 08b7dc3ea..f28a70ef7 100644 --- a/packages/cli/tests/workflow-fork.test.ts +++ b/packages/cli/tests/workflow-fork.test.ts @@ -86,7 +86,7 @@ const DIVERGENT = [ /** * A document that declares a property and writes it, so what a fork inherited - * is visible in what it did rather than only in what it retained. + * is visible in what it did rather than only in what it saved. */ const PROPPED = [ "---", @@ -304,13 +304,13 @@ function* history(fixture: Fixture, runId: string): Operation { return JSON.parse(answered.stdout); } -/** The retained row for one durable operation type, which the fork selects. */ +/** The saved row for one durable operation type, which the fork selects. */ function at(entries: readonly HistoryRow[], type: string): HistoryRow { const row = entries.find( (entry) => entry.event.type === "yield" && entry.event.description?.type === type, ); if (row === undefined) { - throw new Error(`no retained ${type} event: ${entries.map(named).join(", ")}`); + throw new Error(`no saved ${type} event: ${entries.map(named).join(", ")}`); } return row; } @@ -383,7 +383,7 @@ function killedFork( } /** - * Every retained event id, read straight from the fork's own database. + * Every saved event id, read straight from the fork's own database. * * Synchronous because the condition it feeds is: `when()` polls a predicate * that cannot suspend, and an absent, half-written or uncommitted database is @@ -511,7 +511,7 @@ describe("Tier WFF — xmd workflow fork", () => { }); }); - it("WFF3: the fork owns its inherited prefix and its own retained source", function* () { + it("WFF3: the fork owns its inherited prefix and its own saved source", function* () { yield* useFixture({ [DEFINITION]: SOURCE }, function* (fixture) { { yield* xmd(fixture, ["workflow", "start", "--id=source-1", DEFINITION]).expect(); @@ -556,8 +556,8 @@ describe("Tier WFF — xmd workflow fork", () => { expect(resumed.stderr).toContain("workflow status: completed"); // The corrected document is what the fork retains, and the source's is // not. Read from the fork's own journal rather than from what was - // rendered, because a command's output is retained history rather than - // displayed text — and it is the retained history a reload of either + // rendered, because a command's output is saved history rather than + // displayed text — and it is the saved history a reload of either // deleted file would have changed. expect(committedExec(workflowRunPath(fixture.runs, "fork-1")).stdout).toBe("corrected\n"); } @@ -683,7 +683,7 @@ describe("Tier WFF — xmd workflow fork", () => { yield* commit(fixture, { [DEFINITION]: PROPPED_CORRECTED }, "corrected"); // The property is not restated. A corrected definition is still a run of - // the same procedure, and the source retained what it was started with. + // the same procedure, and the source saved what it was started with. const inheriting = yield* xmd(fixture, [ "workflow", "fork", @@ -726,7 +726,7 @@ describe("Tier WFF — xmd workflow fork", () => { ]).join(); expect(conflicting.code).toBe(1); expect(conflicting.stderr).toContain("props"); - // The value that conflicts is retained history and is not repeated back. + // The value that conflicts is saved history and is not repeated back. expect(conflicting.stderr).not.toContain("nightly"); expect(props(yield* snapshot(fixture, "fork-overridden"))).toEqual({ channel: "beta" }); @@ -894,7 +894,7 @@ describe("Tier WFF — xmd workflow fork", () => { }); }); - it("WFF7: history forkability is stable, cumulative and free of retained values", function* () { + it("WFF7: history forkability is stable, cumulative and free of saved values", function* () { yield* useFixture({ [DEFINITION]: SOURCE }, function* (fixture) { yield* xmd(fixture, ["workflow", "start", "--id=source-1", DEFINITION]).expect(); @@ -923,7 +923,7 @@ describe("Tier WFF — xmd workflow fork", () => { const rows = human.stdout.split("\n").filter((line) => line.includes("yes")); expect(rows.length).toBeGreaterThan(0); - // Which checkpoints are *not* forkable is decided from retained event + // Which checkpoints are *not* forkable is decided from saved event // types, and a history holding one is not something this fixture can // produce on purpose. Tier WFK exercises every blocker directly. }); diff --git a/packages/cli/tests/workflow-host.test.ts b/packages/cli/tests/workflow-host.test.ts index 9075dcb02..3470de92b 100644 --- a/packages/cli/tests/workflow-host.test.ts +++ b/packages/cli/tests/workflow-host.test.ts @@ -134,7 +134,7 @@ describe("Tier WFH — workflow host boundary", () => { return; } expect(result.stderr).toContain(UNSUPPORTED); - // Refused before the store is created, so nothing was retained and no + // Refused before the store is created, so nothing was saved and no // delivery line was written. expect(result.stdout).not.toContain("workflow answer:"); expect(yield* exists(fixture.runs)).toBe(false); diff --git a/packages/cli/tests/workflow-inspection.test.ts b/packages/cli/tests/workflow-inspection.test.ts index 57fbc5268..f3fc927f0 100644 --- a/packages/cli/tests/workflow-inspection.test.ts +++ b/packages/cli/tests/workflow-inspection.test.ts @@ -159,7 +159,7 @@ const TOKEN_KIND = "wfi-canary-kind-8f2a1c"; const TOKEN_VALUE = "wfi-canary-value-4d7b6e"; /** - * Give a run a retained Prompt checkpoint, the way a run with an Agent has one. + * Give a run a saved Prompt checkpoint, the way a run with an Agent has one. * * Planted rather than earned: what an adapter puts on a response is proven * elsewhere, and this suite is asking a different question — whether a run @@ -345,7 +345,7 @@ describe("Tier WFI — xmd workflow status, list and history", () => { ); }); - it("WFI3: history JSON holds every retained event, its root and its authored source", function* () { + it("WFI3: history JSON holds every saved event, its root and its authored source", function* () { yield* useFixture({ "flows/release.md": RELEASE }, function* (fixture) { yield* xmd(fixture, ["workflow", "start", "--id=release-1", "flows/release.md"]).expect(); @@ -361,10 +361,10 @@ describe("Tier WFI — xmd workflow status, list and history", () => { } // The command block was written on the fifth line of the definition, and - // history says so from what the event retained. + // history says so from what the event saved. const command = entries.find((entry) => entry.event.description?.type === "exec"); expect(command?.source).toEqual({ - // The authored position is inside the retained source, so it is named + // The authored position is inside the saved source, so it is named // by the logical path that source has in the bundle. path: "release.md", offset: RELEASE.indexOf("```bash exec"), @@ -398,7 +398,7 @@ describe("Tier WFI — xmd workflow status, list and history", () => { yield* useFixture({ "flows/release.md": RELEASE }, function* (fixture) { yield* xmd(fixture, ["workflow", "start", "--id=partial-1", "flows/release.md"]).expect(); - // The retained shape a suspended or interrupted run has: durable events + // The saved shape a suspended or interrupted run has: durable events // and no root Close. Neither status is reachable from this slice's // commands, so the row is removed directly rather than left untested. const database = new DatabaseSync(workflowRunPath(fixture.runs, "partial-1")); @@ -435,7 +435,7 @@ describe("Tier WFI — xmd workflow status, list and history", () => { }); }); - it("WFI12: a retained Prompt checkpoint reaches no status or history output", function* () { + it("WFI12: a saved Prompt checkpoint reaches no status or history output", function* () { yield* useFixture({ "flows/release.md": RELEASE }, function* (fixture) { yield* xmd(fixture, ["workflow", "start", "--id=release-1", "flows/release.md"]).expect(); const path = workflowRunPath(fixture.runs, "release-1"); @@ -637,16 +637,16 @@ describe("Tier WFI — xmd workflow status, list and history", () => { }); /** - * Tier WFI — inspecting a sealed artifact instead of a retained run. + * Tier WFI — inspecting a sealed artifact instead of a saved run. * * The artifact under test is produced by the real `workflow export` command, so * what these cases inspect is one the lifecycle selected out of a run that * really executed — not a snapshot handed to the private writer. Story 2's XE7 - * already proves every retained family reaches the file, and Story 1's C1–C9 + * already proves every saved family reaches the file, and Story 1's C1–C9 * already proves recognition is total; neither is rebuilt here. * * What is new is that an artifact answers the *same* questions a run answers. - * So the discriminating comparison is field-for-field against the retained + * So the discriminating comparison is field-for-field against the saved * answers captured from the same run before it was exported, rather than a * shape check on the artifact's own output. */ @@ -674,7 +674,7 @@ function* entries(directory: string): Operation { return (yield* readdir(directory)).sort(); } -/** One run, its retained answers, and the artifact exported from it. */ +/** One run, its saved answers, and the artifact exported from it. */ interface Exported { readonly artifact: string; readonly status: unknown; @@ -697,7 +697,7 @@ function* exported(fixture: Fixture, runId: string, name: string): Operation { - it("I1: an artifact answers the retained questions, and says which artifact it is", function* () { + it("I1: an artifact answers the saved questions, and says which artifact it is", function* () { yield* useFixture({ "flows/release.md": RELEASE }, function* (fixture) { const run = yield* exported(fixture, "release-1", "evidence.xmd"); @@ -714,7 +714,7 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", // because where a definition can be fetched from now is a permission // belonging to the machine that exported rather than a fact about the run. const { artifact: identity, ...lifecycle } = projected; - const { retrieval: _retained, ...expected } = run.status as Record; + const { retrieval: _saved, ...expected } = run.status as Record; expect(lifecycle).toEqual(expected); expect(identity.identity).toMatch(/^[0-9a-f]{64}$/); expect(identity.frontier.sourceRunId).toBe("release-1"); @@ -746,7 +746,7 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", expect(JSON.parse(forkable.stdout)).toEqual(envelope); // Human output names the file, the artifact and the boundary, above the - // same rendering a retained run gets. + // same rendering a saved run gets. const human = yield* xmd(fixture, [ "workflow", "status", @@ -774,7 +774,7 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", // The two facts coincide in every ordinary run: an effect publishes a // root and then appends its result against it, so the last event's root - // is the current pointer. They are still two retained facts, and a + // is the current pointer. They are still two saved facts, and a // projection that read one as the other would be right by accident. So // the run is edited from outside XMD to make them differ — the only way // to tell the two projections apart at all. @@ -803,11 +803,11 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", // A fixture that could not arrange the divergence would prove nothing. expect(earlier).toBeDefined(); - const retained = JSON.parse( + const saved = JSON.parse( (yield* xmd(fixture, ["workflow", "status", "release-1", "--json"]).expect()).stdout, ); - expect(retained.journalFrontier.workspaceRootId).toBe(earlier); - expect(retained.currentWorkspaceRootId).not.toBe(earlier); + expect(saved.journalFrontier.workspaceRootId).toBe(earlier); + expect(saved.currentWorkspaceRootId).not.toBe(earlier); const artifact = join(fixture.repository, "diverged.xmd"); yield* xmd(fixture, ["workflow", "export", "release-1", `--output=${artifact}`]).expect(); @@ -816,13 +816,11 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", .stdout, ); - expect(projected.journalFrontier).toEqual(retained.journalFrontier); - expect(projected.currentWorkspaceRootId).toBe(retained.currentWorkspaceRootId); + expect(projected.journalFrontier).toEqual(saved.journalFrontier); + expect(projected.currentWorkspaceRootId).toBe(saved.currentWorkspaceRootId); // The artifact's own frontier still names the current pointer, which is // the other of the two facts. - expect(projected.artifact.frontier.currentWorkspaceRootId).toBe( - retained.currentWorkspaceRootId, - ); + expect(projected.artifact.frontier.currentWorkspaceRootId).toBe(saved.currentWorkspaceRootId); }); }); @@ -922,7 +920,7 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", // The same answers, with no run store to answer from. const { artifact: _identity, ...lifecycle } = JSON.parse(status.stdout); - const { retrieval: _retained, ...expected } = run.status as Record; + const { retrieval: _saved, ...expected } = run.status as Record; expect(lifecycle).toEqual(expected); expect(JSON.parse(history.stdout).entries).toEqual(run.history); @@ -1084,7 +1082,7 @@ describe("Tier WFI — xmd workflow status and history over a sealed artifact", const entries = JSON.parse(historyJson.stdout).entries; expect(entries.length).toBe(finalizedArtifact().journal.length); // The intrinsic classification is the one already delivered: an Agent - // turn is unforkable, and retained portability evidence does not change + // turn is unforkable, and saved portability evidence does not change // that here. expect(forkable.stdout).toContain("agent-state-unavailable"); expect(JSON.parse(forkableJson.stdout).entries.length).toBe(entries.length); diff --git a/packages/cli/tests/workflow-installation.test.ts b/packages/cli/tests/workflow-installation.test.ts index b9ef44377..f2f15e3c7 100644 --- a/packages/cli/tests/workflow-installation.test.ts +++ b/packages/cli/tests/workflow-installation.test.ts @@ -34,7 +34,7 @@ import { GitQuery } from "@executablemd/git/api"; import type { WorkflowRunDatabase, WorkflowRunStatus } from "@executablemd/workflow"; import { runWorkflow } from "../src/workflow.ts"; import type { WorkflowExecution, WorkflowHost, WorkflowRequest } from "../src/workflow.ts"; -import { retainedSource } from "../../workflow/tests/support/storage.ts"; +import { savedSource } from "../../workflow/tests/support/storage.ts"; /** * The fixture repository, answered through the Git Api itself. @@ -288,7 +288,7 @@ describe("Tier WFI — what a run hands to canonical core", () => { const execution = seen[0]; expect(execution).toBeDefined(); // The run's own installation carries both halves of the contract: the - // mandatory retained-history admission core applies inside its own journal + // mandatory saved-history admission core applies inside its own journal // read, and the durable preparation core invokes inside the durable root. // It is the only one that prepares — the others contribute admissions and // nothing that runs inside the durable root. @@ -568,7 +568,7 @@ describe("Tier WFI — what a run hands to canonical core", () => { } }); - it("WFI7: an interruption that was retained keeps the ordinary outcome", function* () { + it("WFI7: an interruption that was saved keeps the ordinary outcome", function* () { const attempted: string[] = []; const reported: string[] = []; const running = withResolvers(); @@ -629,7 +629,7 @@ function* startedRun(root: string): Operation { const contents = "recorded\n"; const objectId = (yield* git(repository, ["rev-parse", "HEAD:workflow.md"])).trim(); const objectFormat = (yield* git(repository, ["rev-parse", "--show-object-format"])).trim(); - const retained = yield* retainedSource("workflow.md", contents); + const saved = yield* savedSource("workflow.md", contents); return yield* scoped(function* () { // The same substituted boundary the cases install. Beginning a version-1 @@ -651,8 +651,8 @@ function* startedRun(root: string): Operation { runId, action: "start", creation: { - definition: retained.definition, - sourceSnapshot: retained.sourceSnapshot, + definition: saved.definition, + sourceSnapshot: saved.sourceSnapshot, props: {}, retrieval: { kind: "local-checkout", checkout: repository }, }, diff --git a/packages/cli/tests/workflow-lifecycle-control.test.ts b/packages/cli/tests/workflow-lifecycle-control.test.ts index a45f418fd..1d7d815ef 100644 --- a/packages/cli/tests/workflow-lifecycle-control.test.ts +++ b/packages/cli/tests/workflow-lifecycle-control.test.ts @@ -32,7 +32,7 @@ import { collect, inlineSource, registerComponents } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import { runWorkflow } from "../src/workflow.ts"; import type { WorkflowExecution, WorkflowHost, WorkflowRequest } from "../src/workflow.ts"; -import { retainedSource } from "../../workflow/tests/support/storage.ts"; +import { savedSource } from "../../workflow/tests/support/storage.ts"; interface Fixture { readonly repository: string; @@ -101,7 +101,7 @@ describe("Tier WFC2 — xmd workflow cancel and delete", () => { }); }); - it("WFC2-2: cancelling a resumable run succeeds and reports the retained status", function* () { + it("WFC2-2: cancelling a resumable run succeeds and reports the saved status", function* () { yield* useFixture(function* (fixture) { yield* xmd(fixture, ["workflow", "start", "--id=release-1", "flow.md"]).expect(); // A run whose outcome has not won: the only way to reach one from here is @@ -132,7 +132,7 @@ describe("Tier WFC2 — xmd workflow cancel and delete", () => { expect(deleted.stdout).toContain("run-storage"); expect(yield* exists(workflowRunPath(fixture.runs, "release-1"))).toBe(false); - // The empty lock may remain; it is host arrangement, not retained state, + // The empty lock may remain; it is host arrangement, not saved state, // and it is not a category the caller is told about. expect(deleted.stdout).not.toContain("lifecycle-control"); @@ -169,7 +169,7 @@ function* status(fixture: Fixture, runId: string): Operation { } /** - * The retained shape an interrupted run has: an outcome its root never + * The saved shape an interrupted run has: an outcome its root never * recorded. Removing the Close is the only way to reach it from here, since * this fixture's document cannot be made to hang. */ @@ -222,7 +222,7 @@ function* definitionObject(fixture: Fixture): Operation { return result.stdout.trim(); } -/** The definition, answered at the Git boundary the retained run reads across. */ +/** The definition, answered at the Git boundary the saved run reads across. */ function useDefinitionGit(fixture: Fixture, objectId: string): Operation { return GitQuery.around( { @@ -348,7 +348,7 @@ function lifecycleRows(path: string): { status: string; executions: string[] } { } function* startedRun(root: string, repository: string, objectId: string): Operation { - const retained = yield* retainedSource("workflow.md", "recorded\n"); + const saved = yield* savedSource("workflow.md", "recorded\n"); return yield* scoped(function* () { const transitions = yield* useWorkflowRunHost({ @@ -363,8 +363,8 @@ function* startedRun(root: string, repository: string, objectId: string): Operat runId, action: "start", creation: { - definition: retained.definition, - sourceSnapshot: retained.sourceSnapshot, + definition: saved.definition, + sourceSnapshot: saved.sourceSnapshot, props: {}, retrieval: { kind: "local-checkout", checkout: repository }, }, diff --git a/packages/cli/tests/workflow-retention.test.ts b/packages/cli/tests/workflow-retention.test.ts index 226cfbd47..23cc36199 100644 --- a/packages/cli/tests/workflow-retention.test.ts +++ b/packages/cli/tests/workflow-retention.test.ts @@ -2,7 +2,7 @@ * Tier FG — what a workflow run retains (§3.6). * * A workflow owns its journal and names none, so retention cannot be read off a - * `--journal` pathname: its process results are part of the run's retained + * `--journal` pathname: its process results are part of the run's saved * history, which is what a resumed procedure reads back instead of running the * command again. The record is asserted, because displayed output is identical * either way. @@ -100,7 +100,7 @@ describe("Tier FG — workflow retention", () => { /** * A workflow names no `--journal`: it owns its stream. A run that read the * absent pathname as "keep nothing" would leave every process result in the - * retained history empty, and a resume would read back a command it can no + * saved history empty, and a resume would read back a command it can no * longer see the output of. */ it("FG22: a workflow run retains its process results without naming a journal", function* () { @@ -129,21 +129,21 @@ describe("Tier FG — workflow retention", () => { * start of the same bytes under the same logical name is the same run wherever * on this machine those bytes now happen to sit. */ -describe("Tier FG — retained source", () => { - /** A line only the retained document says, so a replay of it is visible. */ - const RETAINED_LINE = "this line is the retained source"; - const RETAINED = [ - "# Retained", +describe("Tier FG — saved source", () => { + /** A line only the saved document says, so a replay of it is visible. */ + const SAVED_LINE = "this line is the saved source"; + const SAVED = [ + "# Saved", "", - RETAINED_LINE, + SAVED_LINE, "", "```bash exec", - `printf 'retained-output'`, + `printf 'saved-output'`, "```", "", ].join("\n"); - function* startRetained(fixture: Fixture, id: string, at: string): Operation { + function* startSaved(fixture: Fixture, id: string, at: string): Operation { const started = yield* runCli(["workflow", "start", `--id=${id}`, at], { cwd: fixture.repository, env: { HOME: fixture.home, XMD_WORKFLOW_RUNS: fixture.runs }, @@ -174,7 +174,7 @@ describe("Tier FG — retained source", () => { { name: "moved", *disturb(fixture: Fixture, at: string): Operation { - yield* writeTextFile(join(fixture.repository, "flows/moved.md"), RETAINED); + yield* writeTextFile(join(fixture.repository, "flows/moved.md"), SAVED); yield* rm(at, { force: true }); }, }, @@ -189,34 +189,34 @@ describe("Tier FG — retained source", () => { it("FG40: an edited, moved or deleted original changes nothing about the run", function* () { for (const { name, disturb } of DISTURBANCES) { yield* useFixture(function* (fixture) { - const at = join(fixture.repository, "flows/retained.md"); - yield* writeTextFile(at, RETAINED); - yield* startRetained(fixture, `retained-${name}`, at); + const at = join(fixture.repository, "flows/saved.md"); + yield* writeTextFile(at, SAVED); + yield* startSaved(fixture, `saved-${name}`, at); yield* disturb(fixture, at); - const resumed = yield* resume(fixture, `retained-${name}`); + const resumed = yield* resume(fixture, `saved-${name}`); expect({ name, code: resumed.code }).toEqual({ name, code: 0 }); - // The retained document, rendered from the run rather than from a file + // The saved document, rendered from the run rather than from a file // that no longer says it — or no longer exists at all. - expect(resumed.stdout).toContain(RETAINED_LINE); + expect(resumed.stdout).toContain(SAVED_LINE); expect(resumed.stdout).not.toContain("Something else entirely"); - // And the command's own retained result, which is what a replay reads + // And the command's own saved result, which is what a replay reads // back instead of running it again. - expect(committedExec(workflowRunPath(fixture.runs, `retained-${name}`)).stdout).toBe( - "retained-output", + expect(committedExec(workflowRunPath(fixture.runs, `saved-${name}`)).stdout).toBe( + "saved-output", ); }); } }); - it("FG43: a damaged retained source refuses, and never falls back to the file", function* () { + it("FG43: a damaged saved source refuses, and never falls back to the file", function* () { yield* useFixture(function* (fixture) { const at = join(fixture.repository, "flows/fallback.md"); - yield* writeTextFile(at, RETAINED); - yield* startRetained(fixture, "fallback-1", at); + yield* writeTextFile(at, SAVED); + yield* startSaved(fixture, "fallback-1", at); - // The run's own retained content stops describing itself. The file it was + // The run's own saved content stops describing itself. The file it was // started from is untouched and still says exactly what it always said — // which is the whole point: a resume that read it would succeed, and a // resume that must not read it refuses. @@ -234,25 +234,25 @@ describe("Tier FG — retained source", () => { } finally { database.close(); } - expect(yield* readTextFile(at)).toBe(RETAINED); + expect(yield* readTextFile(at)).toBe(SAVED); const resumed = yield* resume(fixture, "fallback-1"); expect(resumed.code).toBe(1); expect(resumed.stderr).toContain("disagrees with its own descriptor"); - expect(resumed.stdout).not.toContain(RETAINED_LINE); + expect(resumed.stdout).not.toContain(SAVED_LINE); }); }); it("FG42: another entrypoint, or other bytes, is another run and conflicts", function* () { yield* useFixture(function* (fixture) { - yield* writeTextFile(join(fixture.repository, "flows/named.md"), RETAINED); - yield* startRetained(fixture, "named-1", join(fixture.repository, "flows/named.md")); + yield* writeTextFile(join(fixture.repository, "flows/named.md"), SAVED); + yield* startSaved(fixture, "named-1", join(fixture.repository, "flows/named.md")); const environment = { HOME: fixture.home, XMD_WORKFLOW_RUNS: fixture.runs }; // The same bytes under a different logical name: a different definition, // because source positions and later relative references use the name. - yield* writeTextFile(join(fixture.repository, "flows/renamed.md"), RETAINED); + yield* writeTextFile(join(fixture.repository, "flows/renamed.md"), SAVED); const renamed = yield* runCli( ["workflow", "start", "--id=named-1", join(fixture.repository, "flows/renamed.md")], { cwd: fixture.repository, env: environment }, @@ -261,7 +261,7 @@ describe("Tier FG — retained source", () => { expect(renamed.stderr).toContain("definition"); // The same name over different bytes: also a different definition. - yield* writeTextFile(join(fixture.home, "named.md"), `${RETAINED}\nand one more line\n`); + yield* writeTextFile(join(fixture.home, "named.md"), `${SAVED}\nand one more line\n`); const changed = yield* runCli( ["workflow", "start", "--id=named-1", join(fixture.home, "named.md")], { cwd: fixture.repository, env: environment }, diff --git a/packages/cli/tests/workflow-suspension.test.ts b/packages/cli/tests/workflow-suspension.test.ts index ac610310e..736eba07d 100644 --- a/packages/cli/tests/workflow-suspension.test.ts +++ b/packages/cli/tests/workflow-suspension.test.ts @@ -20,7 +20,7 @@ * is established through the command's own module and the attachment is * `withWorkflowWorkspace` itself — so nothing it depends on is installed by * this suite. Only the agent is a stand-in, in the slot the real agent profile - * fills, because what is under test is that a turn is retained once rather than + * fills, because what is under test is that a turn is saved once rather than * what an agent says. * * One case below is a process after all, and says why: which provider answers @@ -74,7 +74,7 @@ import type { WorkflowStart, } from "../src/workflow.ts"; import { runWorkflowManagement } from "../src/workflow-management.ts"; -import { retainedSource } from "../../workflow/tests/support/storage.ts"; +import { savedSource } from "../../workflow/tests/support/storage.ts"; const SCHEMA = { type: "object", properties: { approved: { type: "boolean" } } }; @@ -185,7 +185,7 @@ function useGit(fixture: Fixture): Operation { } /** Everything a second connection can see of one run. */ -interface Retained { +interface Saved { readonly status: string; readonly executions: { status: string | undefined; reasonEventId: string | undefined }[]; readonly requests: string[]; @@ -208,10 +208,10 @@ function readRecords(path: string): string[] { } /** - * Rewrite every retained record through `edit`. + * Rewrite every saved record through `edit`. * * A continuation admitted under a previous release cannot be produced by - * running this one, so the retained history is edited into the shape that + * running this one, so the saved history is edited into the shape that * release left behind. Nothing else about the run changes — the same source, * roots and selection — so the identity is the only thing a resume can * disagree about. @@ -235,7 +235,7 @@ function rewriteRecords(path: string, edit: (record: string) => string): void { } } -function retained(path: string): Retained { +function saved(path: string): Saved { const database = new DatabaseSync(path, { readOnly: true }); try { const status = String( @@ -284,10 +284,10 @@ function retained(path: string): Retained { * A run with its definition recorded and nothing executed yet. * * Created through the same transitions production uses, so what the executions - * below resume is an ordinary retained run rather than a fixture shape. + * below resume is an ordinary saved run rather than a fixture shape. */ function* createRun(root: string, fixture: Fixture): Operation { - const retained = yield* retainedSource("workflow.md", fixture.contents); + const saved = yield* savedSource("workflow.md", fixture.contents); return yield* scoped(function* () { const transitions = yield* useWorkflowRunHost({ @@ -305,8 +305,8 @@ function* createRun(root: string, fixture: Fixture): Operation { runId, action: "start", creation: { - definition: retained.definition, - sourceSnapshot: retained.sourceSnapshot, + definition: saved.definition, + sourceSnapshot: saved.sourceSnapshot, props: {}, retrieval: { kind: "local-checkout", checkout: fixture.repository }, }, @@ -390,7 +390,7 @@ function manage( }); } -/** The retained answers this run holds, read the way something outside XMD would. */ +/** The saved answers this run holds, read the way something outside XMD would. */ function answers(path: string): { suspensionId: string; state: string; answer: string }[] { const database = new DatabaseSync(path, { readOnly: true }); try { @@ -480,7 +480,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { // Suspension is its own outcome, and it is the exit code a caller reads. expect(first.exitCode).toBe(2); - const afterFirst = retained(path); + const afterFirst = saved(path); expect(afterFirst.status).toBe("suspended"); // The run was created and left resumable before this suite ran, so its @@ -491,7 +491,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { ); expect(firstSuspended).toHaveLength(1); - // The stop reason names the retained request event rather than repeating + // The stop reason names the saved request event rather than repeating // anything the request said. expect(firstSuspended[0]?.reasonEventId).toBe(afterFirst.requestEventId); @@ -522,7 +522,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { ); expect(resumed.exitCode).toBe(2); - const after = retained(path); + const after = saved(path); expect(after.status).toBe("suspended"); // One prior effect and one request across every execution: a resume @@ -532,7 +532,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { expect(after.rootCloses).toBe(0); // One more execution record per attempt, every one settled `suspended`, - // each naming the one retained request. + // each naming the one saved request. const suspended = after.executions.filter((execution) => execution.status === "suspended"); expect(suspended).toHaveLength(attempt + 1); expect(suspended.every((execution) => execution.reasonEventId === after.requestEventId)).toBe( @@ -590,7 +590,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { expect(completed.exitCode).toBe(0); const path = workflowRunPath(root, runId); - const after = retained(path); + const after = saved(path); expect(after.status).toBe("completed"); expect(after.requests).toHaveLength(0); expect(after.rootCloses).toBe(1); @@ -605,7 +605,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { body(), ); expect(replayed.exitCode).toBe(0); - const afterReplay = retained(path); + const afterReplay = saved(path); expect(afterReplay.status).toBe("completed"); expect(afterReplay.priorEffects).toBe(after.priorEffects); expect(afterReplay.requests).toHaveLength(0); @@ -630,7 +630,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { ).toBe(2); const path = workflowRunPath(root, runId); - const suspended = retained(path); + const suspended = saved(path); const suspensionId = suspended.requests[0] ?? ""; expect(suspensionId).not.toBe(""); @@ -653,7 +653,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { // The run is exactly where the suspension left it: same status, same stop // reason, same executions, same history. - const afterDelivery = retained(path); + const afterDelivery = saved(path); expect(afterDelivery).toEqual(suspended); expect(answers(path)).toEqual([ { suspensionId, state: "pending", answer: JSON.stringify({ approved: true }) }, @@ -679,7 +679,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { `continued-with-${JSON.stringify({ approved: true })}`, ]); - const afterResume = retained(path); + const afterResume = saved(path); expect(afterResume.status).toBe("completed"); expect(afterResume.rootCloses).toBe(1); // One request across every execution, and the delivery is spent. @@ -703,7 +703,7 @@ describe("Tier WFS — a suspended run and its no-input resumes", () => { ); const path = workflowRunPath(root, runId); - const suspensionId = retained(path).requests[0] ?? ""; + const suspensionId = saved(path).requests[0] ?? ""; expect(suspensionId).not.toBe(""); // A wait this run is not at, refused without writing anything. @@ -824,7 +824,7 @@ const FILE_EFFECT = WORKSPACE_FILE; /** * What core's own declaration offers the paired `` as. * - * Spelled here rather than imported, because a case about what a retained + * Spelled here rather than imported, because a case about what a saved * admission holds has to name the string the journal holds: reading it from the * component the resume also reads would agree with itself after a rename. */ @@ -839,7 +839,7 @@ interface AgentCalls { * A root Agent provider that answers without a process. * * Test-only, and the smallest thing that makes `` a real durable turn: - * what is under test is that the turn is retained once, not what an agent says. + * what is under test is that the turn is saved once, not what an agent says. */ function* useStubAgent(calls: AgentCalls): Operation { // Installed on the caller's scope, not a nested one: a `scoped()` here would @@ -1072,7 +1072,7 @@ function counts(path: string): Record { }; } -/** Every durable yield of one description type this run retained. */ +/** Every durable yield of one description type this run saved. */ function journalled(path: string, type: string): number { const database = new DatabaseSync(path, { readOnly: true }); try { @@ -1092,10 +1092,10 @@ function journalled(path: string, type: string): number { } /** The Workspace roots this run has published, and the one it stands on. */ -function workspaceRootState(path: string): { retained: number; current: string } { +function workspaceRootState(path: string): { saved: number; current: string } { const database = new DatabaseSync(path, { readOnly: true }); try { - const retained = Number( + const saved = Number( database.prepare("SELECT COUNT(*) AS count FROM workspace_roots").get()?.["count"], ); const current = String( @@ -1103,13 +1103,13 @@ function workspaceRootState(path: string): { retained: number; current: string } .prepare("SELECT current_root_id FROM workspace_state WHERE singleton_id = 1") .get()?.["current_root_id"], ); - return { retained, current }; + return { saved, current }; } finally { database.close(); } } -/** Every durable yield this run retained, in journal order, as type and name. */ +/** Every durable yield this run saved, in journal order, as type and name. */ function orderedEffects(path: string): { type: string; name: string }[] { const database = new DatabaseSync(path, { readOnly: true }); try { @@ -1189,16 +1189,16 @@ describe("Tier CKX — a checkpoint a document asked for", () => { ]); const path = workflowRunPath(root, runId); - const suspended = retained(path); + const suspended = saved(path); expect(suspended.status).toBe("suspended"); expect(suspended.requests).toHaveLength(1); expect(suspended.rootCloses).toBe(0); - // The wait the caller was told to answer is the wait the run retained — + // The wait the caller was told to answer is the wait the run saved — // read back from the journal rather than from the line that announced it. expect(reported).toBe(suspended.requests[0]); - // The stop reason names the retained request rather than repeating it. + // The stop reason names the saved request rather than repeating it. const settled = suspended.executions.filter((execution) => execution.status === "suspended"); expect(settled).toHaveLength(1); expect(settled[0]?.reasonEventId).toBe(suspended.requestEventId); @@ -1255,7 +1255,7 @@ describe("Tier CKX — a checkpoint a document asked for", () => { expect(rendered).toEqual([]); // CK3 — the delivery retains a value and moves nothing. - const suspended = retained(path); + const suspended = saved(path); const suspensionId = suspended.requests[0] ?? ""; const delivered = yield* manage( { action: "answer", runId, suspensionId, value: { proceed: true }, secretDetection: true }, @@ -1264,7 +1264,7 @@ describe("Tier CKX — a checkpoint a document asked for", () => { expect(delivered.exitCode).toBe(0); expect(delivered.written.out).toEqual([`workflow answer: ${runId} (${suspensionId})`]); expect(delivered.written.err).toEqual([]); - expect(retained(path)).toEqual(suspended); + expect(saved(path)).toEqual(suspended); expect(calls.prompts).toHaveLength(1); // CK4 — the resume spends the answer, and repeats none of the three. @@ -1276,7 +1276,7 @@ describe("Tier CKX — a checkpoint a document asked for", () => { ); expect(resumed.exitCode).toBe(0); - const completed = retained(path); + const completed = saved(path); expect(completed.status).toBe("completed"); expect(completed.requests).toHaveLength(1); expect(completed.requests[0]).toBe(suspended.requests[0]); @@ -1309,10 +1309,10 @@ describe("Tier CKX — a checkpoint a document asked for", () => { .slice("workflow run: ".length) .trim(); const path = workflowRunPath(root, runId); - const suspensionId = retained(path).requests[0] ?? ""; + const suspensionId = saved(path).requests[0] ?? ""; expect(suspensionId).not.toBe(""); - // An answer the retained response schema rejects. `proceed` is the field + // An answer the saved response schema rejects. `proceed` is the field // `` declared, and this is the shape its schema gives it — so the // refusal is by the schema the document compiled, not one delivery guessed. const before = yield* storageDigest(path); @@ -1399,7 +1399,7 @@ decision: {decision.proceed} component: `"} allow={["read"]} as="observed" />\n`, setup: ` -the retained note +the saved note `, nested: [{ operation: "read", target: "notes.md" }], @@ -1443,7 +1443,7 @@ the retained note // parent effect committed ahead of the wait. The journal counts and the // Workspace root publications captured here are the once-only baseline // everything after the suspension is held to. - const suspended = retained(path); + const suspended = saved(path); expect(suspended.status).toBe("suspended"); expect(suspended.requests).toHaveLength(1); expect(rendered).toEqual([]); @@ -1485,7 +1485,7 @@ the retained note productionHost(store, calls), ); expect(delivered.exitCode).toBe(0); - expect(retained(path)).toEqual(suspended); + expect(saved(path)).toEqual(suspended); expect(counts(path)).toEqual(atSuspension); expect(workspaceRootState(path)).toEqual(rootsAtSuspension); @@ -1500,7 +1500,7 @@ the retained note ); expect(resumed.exitCode).toBe(0); - const completed = retained(path); + const completed = saved(path); expect(completed.status).toBe("completed"); expect(counts(path)).toEqual(atSuspension); expect(workspaceRootState(path)).toEqual(rootsAtSuspension); @@ -1548,7 +1548,7 @@ the retained note .trim(); const path = workflowRunPath(store, runId); - const suspended = retained(path); + const suspended = saved(path); expect(suspended.status).toBe("suspended"); // The admission carries the identity core's own declaration offers the // paired spelling as, in the write table that admitted it — so that is what @@ -1562,7 +1562,7 @@ the retained note ).toBe(true); // And the generated `` really executed before the run suspended: the - // retained file effects hold an ensure for `/generated` ahead of the nested + // saved file effects hold an ensure for `/generated` ahead of the nested // write beneath it. Read from the committed order rather than from the // fragment's text, because the text says what was asked for and the order // says what happened. @@ -1606,7 +1606,7 @@ the retained note // It really resumes. expect(resumed.exitCode).toBe(0); - expect(retained(path).status).toBe("completed"); + expect(saved(path).status).toBe("completed"); expect(rendered).toHaveLength(1); // And the resume publishes nothing further: no additional effect, and no // new Workspace root. diff --git a/packages/code-review-agent/src/review-components.ts b/packages/code-review-agent/src/review-components.ts index 446d22eed..53b59123f 100644 --- a/packages/code-review-agent/src/review-components.ts +++ b/packages/code-review-agent/src/review-components.ts @@ -181,7 +181,7 @@ function reviewComponent(fn: (props: any) => Operation): FunctionCompon * Reserve the seven TypeScript component names alongside the Markdown declarations. * These implementations run processes, read credentials and reach the network; * leaving them shadowable would let the review subject replace its own reviewers. - * Their existing public names and contracts are retained. + * Their existing public names and contracts are saved. */ export const REVIEW_REGISTRATIONS: readonly ComponentRegistration[] = [ { diff --git a/packages/core/host.ts b/packages/core/host.ts index 4558e1520..e88fd23be 100644 --- a/packages/core/host.ts +++ b/packages/core/host.ts @@ -6,7 +6,7 @@ * An installation carries the two things only a host may contribute, and both * are acts of infrastructure rather than of authoring: * - * - **Admissions** constrain a retained history *before* it is replayed from. + * - **Admissions** constrain a saved history *before* it is replayed from. * They decide what a journal must already say for this execution to be * allowed to continue it. * - **Preparations** perform trusted durable work *inside* the durable root, @@ -34,7 +34,7 @@ * * Both stop at the first refusal, and a refusal stops only what has not * happened yet: durable effects an earlier preparation completed stay - * retained and are not rolled back, and the durable root records the refusal as + * saved and are not rolled back, and the durable root records the refusal as * its own terminal. Because core binds such a terminal to the exact root source * and target it was about, an identical execution replays that failure instead * of finding a history it cannot read. @@ -75,7 +75,7 @@ export { executeInstalled } from "./src/execute.ts"; * * It is exported here and nowhere else. `execute()` does not take it, no * authoring export names it, and no installation carries it — a document that - * could name what a previous execution retained could ask for it, and the only + * could name what a previous execution saved could ask for it, and the only * host with a predecessor to inherit from is the one that kept the journal. */ export type { @@ -102,7 +102,7 @@ export type { ComponentAnswerRequest, } from "./src/component-answers.ts"; /** - * The response shape a retained request answers with, and the detaching a host + * The response shape a saved request answers with, and the detaching a host * needs to read one. * * The record an authored `` retains, through the same code, so a @@ -310,9 +310,9 @@ export type { } from "./src/test-harness.ts"; /** - * The durable Agent Prompt record, for a host that reads a retained journal. + * The durable Agent Prompt record, for a host that reads a saved journal. * - * A sealed workflow artifact classifies each Agent session by what its retained + * A sealed workflow artifact classifies each Agent session by what its saved * Prompts say, and the only thing that can answer that is the parser the live * run already records through. A second reading of the same durable value would * be a second contract, so the parser and the effect type it belongs to cross diff --git a/packages/core/mod.ts b/packages/core/mod.ts index 9a5f0a39f..7f5eab01c 100644 --- a/packages/core/mod.ts +++ b/packages/core/mod.ts @@ -177,13 +177,13 @@ export { formatDocumentReference, INLINE_SOURCE_PATH, inlineSource, - retainedSource, + savedSource, rootSourcePath, } from "./src/root-source.ts"; export type { FileRootDocument, InlineRootDocument, - RetainedRootDocument, + SavedRootDocument, RootDocumentSource, } from "./src/root-source.ts"; export { @@ -192,7 +192,7 @@ export { isDocumentTargetError, // The one decision on what an exact target looks like. Exported under the // fuller name because a consumer outside this package — a stored workflow - // definition validating the target it retained — reads it beside its own + // definition validating the target it saved — reads it beside its own // vocabulary, where "target" alone would not say target of what. isCanonicalTarget as isCanonicalDocumentTarget, parseDocumentTargetFailure, diff --git a/packages/core/src/agent/agent-api.ts b/packages/core/src/agent/agent-api.ts index 3e1d33e8f..f02741557 100644 --- a/packages/core/src/agent/agent-api.ts +++ b/packages/core/src/agent/agent-api.ts @@ -199,7 +199,7 @@ export interface AgentApi { * * This answers nothing. A return value is not evidence a launch happened, and * the invocation that issued the request ignores whatever comes back: the - * only thing that settles a launch is what it retained. Middleware may + * only thing that settles a launch is what it saved. Middleware may * inspect the request, narrow it with `with()`, refuse by throwing, or * delegate it onward. */ diff --git a/packages/core/src/agent/checkpoint.ts b/packages/core/src/agent/checkpoint.ts index c7433aa5b..09df5a80b 100644 --- a/packages/core/src/agent/checkpoint.ts +++ b/packages/core/src/agent/checkpoint.ts @@ -104,8 +104,8 @@ function parseCheckpoint(value: unknown): AgentPromptCheckpoint { /** * Read a checkpoint out of a value nobody has authenticated, or none. * - * The total counterpart of the parser above, for a reader holding retained - * bytes rather than something a provider just handed over. A retained + * The total counterpart of the parser above, for a reader holding saved + * bytes rather than something a provider just handed over. A saved * checkpoint that does not read back is not a checkpoint, and a caller that * needed one says so itself — this reports absence and refuses nothing. */ diff --git a/packages/core/src/agent/function-components.ts b/packages/core/src/agent/function-components.ts index f2a6bdcc9..1e9b3a53b 100644 --- a/packages/core/src/agent/function-components.ts +++ b/packages/core/src/agent/function-components.ts @@ -502,7 +502,7 @@ function* runPrompt( // authentic use answers with what it was verified under, and a raw session // answers with nothing. // - // Retained only once the provider said the turn started, because that is when + // Saved only once the provider said the turn started, because that is when // the conversation was under these settings: a prompt that failed while they // were still being applied ran under nothing, and one that started and then // failed still ran under exactly them. @@ -552,9 +552,9 @@ function* runPrompt( * and launches nothing. * * The launch itself is the provider's, and every phase it completes is - * retained through the journal installed here. That is also the coordination + * saved through the journal installed here. That is also the coordination * boundary: middleware around the Agent Api may observe a launch or refuse - * one, but a result that arrives without those retained phases describes a + * one, but a result that arrives without those saved phases describes a * launch that did not happen, and is refused. */ /** @@ -564,7 +564,7 @@ function* runPrompt( * `Agent.launch()`, which a repository function component may call directly. * A second implementation here is what let the component and the programmatic * caller disagree about what a launch is — and it is why an exit the provider - * never retained used to be able to settle one. + * never saved used to be able to settle one. */ export function* SessionLaunch(props: Record): Operation { const instructions = (yield* hasContent()) ? String(yield* content()) : ""; diff --git a/packages/core/src/agent/journal.ts b/packages/core/src/agent/journal.ts index 058b4d036..b5d4283cf 100644 --- a/packages/core/src/agent/journal.ts +++ b/packages/core/src/agent/journal.ts @@ -41,7 +41,7 @@ * The prompt itself has already finished by then. Talking to a provider happens * outside all of this, and only its outcome reaches a publisher. * - * Replay reaches none of it. A retained entry answers before the live path + * Replay reaches none of it. A saved entry answers before the live path * exists, so a replayed prompt contacts no provider, opens no transaction and * associates nothing. */ @@ -138,7 +138,7 @@ export interface PromptRecord { * Ordinarily a checkpoint is not journalled at all: it is associated with one * terminal event and published through a host's own transaction, because what * a host keeps beside a Prompt is that host's business. A launch is the one - * caller that has to keep it here — the turn it owes is retained so a replay + * caller that has to keep it here — the turn it owes is saved so a replay * never spends a second one, and the record of the turn is only evidence that * it happened if it names which turn it was. */ @@ -619,7 +619,7 @@ function serializePermission(permission: PromptPermission): Json { return payload; } -/** What a retained audit that does not read back is, as distinct from none. */ +/** What a saved audit that does not read back is, as distinct from none. */ const UNREADABLE = Symbol("unreadable permission audit"); /** @@ -726,7 +726,7 @@ function isRecord(value: unknown): value is Record { * One durable `agent_prompt` result, as the record it describes itself as. * * Pure and total: it reads a value nobody has authenticated and answers with - * the record or with nothing, so a caller that has only retained bytes — a + * the record or with nothing, so a caller that has only saved bytes — a * sealed artifact's verifier, for one — asks the same question a live run * asks rather than spelling the shape a second time. */ diff --git a/packages/core/src/agent/launch-coordinator.ts b/packages/core/src/agent/launch-coordinator.ts index ff387ece6..6eba533b4 100644 --- a/packages/core/src/agent/launch-coordinator.ts +++ b/packages/core/src/agent/launch-coordinator.ts @@ -47,7 +47,7 @@ import type { * * `detach` and `exit` receive the preparation carrying the identity the launch * is handing over, which for a launch that owed a turn is the one that turn - * asserted rather than the empty one the preparation was retained with. + * asserted rather than the empty one the preparation was saved with. */ export interface AgentLaunchPhases { prepare(): Operation; @@ -73,7 +73,7 @@ export interface AgentLaunchCoordinator { * * The only record this can author is a prepared failure. A provider that * cannot start — no capability, a busy session, a session whose last owner - * never proved it stopped — says so here, and nothing later is retained. + * never proved it stopped — says so here, and nothing later is saved. */ refuse(request: AgentLaunchRequest, preparation: PreparedLaunchRecord): Operation; /** @@ -151,20 +151,20 @@ export interface LaunchRetention { export interface LiveLaunch { issued: IssuedLaunch; retention: LaunchRetention; - /** What the retained records settled to, once every phase is accepted. */ + /** What the saved records settled to, once every phase is accepted. */ settled?: SessionLaunchResult; - /** The retained preparation, so a refusal is reportable to the caller. */ + /** The saved preparation, so a refusal is reportable to the caller. */ preparation?: PreparedLaunchRecord; - /** The retained materialization, for a launch that owed a turn. */ + /** The saved materialization, for a launch that owed a turn. */ materialization?: MaterializedLaunchRecord; - /** The retained detach, so a handoff that stopped there says why. */ + /** The saved detach, so a handoff that stopped there says why. */ detachment?: DetachedLaunchRecord; - /** The retained exit, so how the native UI ended is the caller's answer. */ + /** The saved exit, so how the native UI ended is the caller's answer. */ exit?: ExitedLaunchRecord; } /** - * Why a retained preparation cannot be the one that was asked for. + * Why a saved preparation cannot be the one that was asked for. * * The provider asserts the session facts, but the request is the document's. * A record that changed what was asked would make the journal describe a launch @@ -251,7 +251,7 @@ export function createLaunchCoordinator( const prepared = yield* launch.retention.prepared(() => phases.prepare()); launch.preparation = prepared; if (prepared.failure) { - // A refusal the provider retained through its own preparation. It stops + // A refusal the provider saved through its own preparation. It stops // here exactly as `refuse()` would; nothing later is authored. return; } @@ -309,7 +309,7 @@ export function createLaunchCoordinator( materialized.requestId !== plan.requestId ) { throw new AgentLaunchProtocolError( - "the provider retained a materialization turn other than the one this launch planned", + "the provider saved a materialization turn other than the one this launch planned", ); } if (materialized.failure) { @@ -337,7 +337,7 @@ export function createLaunchCoordinator( return; } // The handoff itself completed; how the native UI then ended is a - // separate fact, and one that is retained rather than settled. A launch + // separate fact, and one that is saved rather than settled. A launch // whose UI died is not a launch to run again. if (exited.signal !== undefined || exited.exitCode !== 0) { return; diff --git a/packages/core/src/agent/launch-journal.ts b/packages/core/src/agent/launch-journal.ts index 322d476de..29bd54700 100644 --- a/packages/core/src/agent/launch-journal.ts +++ b/packages/core/src/agent/launch-journal.ts @@ -3,9 +3,9 @@ * §Durability and replay). * * One launch is one `agent_session_launch` effect type, named from the - * expansion identity `` derived, with one retained record per + * expansion identity `` derived, with one saved record per * phase the launch completed. Phases are separate durable operations because - * the preparation has to be retained *before* ACP ownership is released: a + * the preparation has to be saved *before* ACP ownership is released: a * launch interrupted between detach and spawn has to resume against the * provider session it already prepared, and a record written only when the * whole launch settles would describe nothing at all. @@ -16,9 +16,9 @@ * * The one exception is the materialization turn, and it is not an exception to * that reasoning: it is a model turn, sent to an agent session, and it is - * retained as an ordinary `agent_prompt` because that is what it is. What must + * saved as an ordinary `agent_prompt` because that is what it is. What must * never be re-run is the sending, and `agent_prompt` is the durable operation - * that already guarantees that — which is the whole reason the turn is retained + * that already guarantees that — which is the whole reason the turn is saved * as a prompt first and as the `materialized` phase second. */ @@ -50,7 +50,7 @@ import type { PermissionMode } from "./agent-api.ts"; const AGENT_SESSION_LAUNCH = "agent_session_launch"; /** - * Everything the engine owns about a launch request, retained in the + * Everything the engine owns about a launch request, saved in the * preparation effect's description. * * The rendered instructions are the effect's `input`; the rest describes the @@ -158,7 +158,7 @@ function exactMembers(value: Record, members: readonly string[] } /** - * Read a retained build binding strictly. + * Read a saved build binding strictly. * * The member set is exact rather than minimal, because a binding is compared * for equality: a member this build ignores is a fact the writer thought was @@ -207,10 +207,10 @@ function serializeBinding(binding: ExecutableBuildBindingV1): Json { const PLAN_MEMBERS = ["promptVersion", "requestId", "prompt"]; /** - * Read a retained materialization plan strictly. + * Read a saved materialization plan strictly. * * Exact members, like a build binding and for the same reason: the plan is what - * a resumed launch checks the retained turn against, and a member this build + * a resumed launch checks the saved turn against, and a member this build * ignores is one the writer thought identified the turn. */ function materializationPlan(value: unknown): MaterializationPlan | undefined { @@ -273,7 +273,7 @@ function serializePrepared(record: PreparedLaunchRecord): Json { } /** - * Read a retained preparation strictly. + * Read a saved preparation strictly. * * Exported from this internal module so the strictness can be asserted * directly. It is not part of the package's public surface: `packages/core/mod.ts` @@ -712,14 +712,14 @@ function materializedFromPrompt( /** * What a launch retains when it cannot tell whether the turn was already spent. * - * The preparation is retained immediately before the turn is sent, so a run that + * The preparation is saved immediately before the turn is sent, so a run that * replays `prepared` and then finds no outcome for the turn is looking at a run * that reached the sending point and never recorded what happened there. Nothing * reachable from here separates a turn that was never sent from one the backend * accepted and the interrupted run never saw — and sending now, if it is the * second, spends a turn in someone's conversation to learn that. * - * So the launch stops. This is retained rather than raised bare, so every later + * So the launch stops. This is saved rather than raised bare, so every later * replay says the same thing without reconsidering it. */ function unrecoverable(plan: MaterializationPlan): MaterializedLaunchRecord { @@ -748,7 +748,7 @@ function unrecoverable(plan: MaterializationPlan): MaterializedLaunchRecord { * second, which is the whole reason the prompt is appended first. * * `preparedThisRun` says whether this run is the one that prepared the session, - * rather than a resume reading a preparation an earlier run retained. It decides + * rather than a resume reading a preparation an earlier run saved. It decides * the one thing a resume must not decide for itself: reaching the live path of a * turn whose preparation replayed means an earlier run already stood where this * one is standing, so this run refuses instead of sending. diff --git a/packages/core/src/agent/launch-owner.ts b/packages/core/src/agent/launch-owner.ts index ae43e4d82..a4b462e0f 100644 --- a/packages/core/src/agent/launch-owner.ts +++ b/packages/core/src/agent/launch-owner.ts @@ -10,7 +10,7 @@ * resolved, because a host with no terminal cannot launch anything and learning * that should not cost an agent probe. The request is issued next, routed * through the public `Agent` chain, and settled only from what the invocation - * retained — never from what came back up the chain. + * saved — never from what came back up the chain. */ import { createApi } from "@effectionx/context-api"; @@ -77,7 +77,7 @@ export function* launchSite(): Operation { identity, cwd: yield* cwd(), // `Agent.AddDir` is not built, so a launch declares no additional roots yet - // and the retained request says so explicitly rather than omitting it. + // and the saved request says so explicitly rather than omitting it. additionalDirectories: [], permissionMode: yield* AgentInternal.operations.permissionMode, }; @@ -104,13 +104,13 @@ export function createLaunchRegistry(): LaunchRegistry { } /** - * Raise a phase that was retained as a refusal. + * Raise a phase that was saved as a refusal. * - * Retained first and raised second, so the phase a later replay resumes from is + * Saved first and raised second, so the phase a later replay resumes from is * the phase that actually happened — including when what happened was the * provider declining to go further. */ -function raiseRetained(phase: LaunchPhase, failure: { class: string; message: string }): never { +function raiseSaved(phase: LaunchPhase, failure: { class: string; message: string }): never { throw new AgentLaunchError(failure.message, { phase, failureClass: parseFailureClass(failure.class), @@ -168,7 +168,7 @@ function unaccepted(request: AgentLaunchRequest, site: LaunchSite): PreparedLaun } /** - * Perform one launch and return what the invocation retained. + * Perform one launch and return what the invocation saved. * * The registry is this document installation's; the coordinator handed to * providers resolves a routed request through it. @@ -188,7 +188,7 @@ export function launchSession( const site = yield* launchSite(); // Resolving here is the availability boundary: an agent that is not there - // fails expansion rather than being retained as a refused launch. + // fails expansion rather than being saved as a refused launch. const agent = yield* Agent.operations.agent(options?.agent); const sessionProp = typeof options?.session === "string" ? options.session : undefined; @@ -213,7 +213,7 @@ export function launchSession( ); // Whether this run is the one that prepared the session, rather than a - // resume reading a preparation an earlier run retained. Only the launch that + // resume reading a preparation an earlier run saved. Only the launch that // did the preparing may send the turn that follows it. let preparedThisRun = false; const launch: LiveLaunch = { @@ -265,11 +265,11 @@ export function launchSession( } if (refused && issued.accepted()) { // The request was accepted and then something violated the protocol. That - // is not "nobody performed it", so it is not retained as one. + // is not "nobody performed it", so it is not saved as one. throw refused; } - // Nobody performed it. That is retained as a refusal rather than raised + // Nobody performed it. That is saved as a refusal rather than raised // bare, so a replay resumes from the phase that actually happened. if (!issued.accepted()) { const record = yield* launch.retention.prepared( @@ -278,23 +278,23 @@ export function launchSession( return unaccepted(request, site); }, ); - raiseRetained("prepared", record.failure!); + raiseSaved("prepared", record.failure!); } if (launch.preparation?.failure) { - raiseRetained("prepared", launch.preparation.failure); + raiseSaved("prepared", launch.preparation.failure); } if (launch.materialization?.failure) { - raiseRetained("materialized", launch.materialization.failure); + raiseSaved("materialized", launch.materialization.failure); } if (launch.detachment?.failure) { - raiseRetained("detached", launch.detachment.failure); + raiseSaved("detached", launch.detachment.failure); } const exited = launch.exit; if (exited?.failure) { - raiseRetained("exited", exited.failure); + raiseSaved("exited", exited.failure); } - // The UI ran and ended badly. Reported from the retained record, so a + // The UI ran and ended badly. Reported from the saved record, so a // replay says the same thing without starting a second one. if (exited?.signal !== undefined) { throw new AgentLaunchError(`the native agent UI was terminated by ${exited.signal}`, { diff --git a/packages/core/src/agent/launch.ts b/packages/core/src/agent/launch.ts index 9106898a1..e30dea989 100644 --- a/packages/core/src/agent/launch.ts +++ b/packages/core/src/agent/launch.ts @@ -13,12 +13,12 @@ * providers that need no such turn go straight from `prepared` to `detached`, * and a launch on them still costs zero model turns. * - * Each phase a provider completes is retained by the invocation that issued + * Each phase a provider completes is saved by the invocation that issued * the launch, through the coordinator core delivers to the selected provider. * The provider hands each phase's work over rather than performing it and * reporting afterwards, so a replay of that phase never runs the work again: a * resumed launch reuses the provider-native identity the first attempt - * retained instead of creating a replacement session. + * saved instead of creating a replacement session. * * That is what keeps operational ownership with the launch invocation. Public * middleware composed around the Agent Api receives a request and may refuse @@ -26,8 +26,8 @@ * it returns settles nothing. * * `launched` is the live state between the spawn and the child's exit. It is - * deliberately not a retained record: an interrupted native process leaves - * `detached` as the last retained phase, and resuming reattaches the native UI + * deliberately not a saved record: an interrupted native process leaves + * `detached` as the last saved phase, and resuming reattaches the native UI * to that same provider session. */ @@ -54,13 +54,13 @@ export type InstructionReconciliation = "installed" | "resumed" | "replaced"; /** * Why a launch stopped, in terms an author can act on. * - * The class is stable and retained; the message is diagnostic. Neither + * The class is stable and saved; the message is diagnostic. Neither * carries provider credentials, adapter settings, executable paths, argv, * environment, temporary paths, or native transcript content. * * `directory-authority` means the launch could not use the directory it was * asked for. It keeps that spelling because it is a serialized durable value: a - * retained record written by an earlier build is parsed by an exact match, so + * saved record written by an earlier build is parsed by an exact match, so * renaming it would stop those records from replaying. */ export type LaunchFailureClass = @@ -118,7 +118,7 @@ export interface LaunchFailure { * supplied it unchanged. Nothing the provider says can replace it, which is * the property that lets a native UI be handed a session to create. * - * Retained rather than inferred, because the two are indistinguishable after + * Saved rather than inferred, because the two are indistinguishable after * the fact: a returned identity and a supplied one are both just a string in * the record. */ @@ -132,7 +132,7 @@ export type IdentityProvenance = "provider-returned" | "client-allocated"; * identity and disagree silently about what it names, so a session whose build * cannot be reproduced is refused rather than resumed. * - * What is retained is deliberately not a path: a path says where a build was, + * What is saved is deliberately not a path: a path says where a build was, * which stops being true, while a version and a digest say which build it was, * which does not. That also keeps the record free of host layout. */ @@ -148,7 +148,7 @@ export interface ExecutableBuildBindingV1 { /** * Whether two bindings name the same build. * - * Equality is over what was retained, so the same build reached through a + * Equality is over what was saved, so the same build reached through a * different path is compatible and a different build at the same path is not. */ export function sameExecutableBuild( @@ -171,7 +171,7 @@ export function sameExecutableBuild( * Exactly one XMD-owned turn closes that gap, and it is planned in the * preparation rather than decided later: the version says which exact prompt * will be sent, and the request id is fixed here so a replay that finds the - * turn already retained is looking for the same turn rather than a new one. + * turn already saved is looking for the same turn rather than a new one. * * Present only for a provider that needs it, and only for a conversation this * launch created. Nothing else acquires a turn by being launched. @@ -182,7 +182,7 @@ export interface MaterializationPlan { /** The request id the turn runs under, immutable once `prepared` is kept. */ readonly requestId: string; /** - * The exact text that will be sent, retained before it is sent. + * The exact text that will be sent, saved before it is sent. * * A version names a prompt; this is the prompt. Keeping the bytes with the * plan is what lets a reader of the journal see what an XMD-owned turn said @@ -195,7 +195,7 @@ export interface MaterializationPlan { * What the provider reported about what the materialization turn cost. * * Every field is optional and a missing one means the provider reported - * nothing, which is displayed and retained as exactly that. Nothing here reads + * nothing, which is displayed and saved as exactly that. Nothing here reads * absence as zero: a turn whose token count was never reported is not a free * turn, and saying so would be inventing an observation. */ @@ -213,7 +213,7 @@ export interface MaterializationUsage { /** * The turn that made a fresh conversation openable, and what it cost. * - * Retained as its own phase because it sits between two facts that must not be + * Saved as its own phase because it sits between two facts that must not be * confused: the session exists (`prepared`) and ACP has let go of it * (`detached`). A launch interrupted after the turn must never spend a second * one, and the only thing that can say the first was spent is a record of it. @@ -221,7 +221,7 @@ export interface MaterializationUsage { * `turn` is the provider's own name for the completed turn, carried across * unchanged. It is the evidence the exchange reached the backend rather than * merely being written to a socket, and it is the same checkpoint the durable - * prompt retained — which is what lets this record be rebuilt from that prompt + * prompt saved — which is what lets this record be rebuilt from that prompt * when a run stopped between the two. */ export interface MaterializedLaunchRecord { @@ -242,14 +242,14 @@ export interface MaterializedLaunchRecord { /** How long the turn took, in milliseconds. Absent when no turn was run. */ durationMs?: number; usage: MaterializationUsage; - /** The assistant's full response text, retained and displayed unabridged. */ + /** The assistant's full response text, saved and displayed unabridged. */ response: string; stopReason?: string; failure?: LaunchFailure; } /** - * What one provider retained about the session it prepared. + * What one provider saved about the session it prepared. * * `nativeSessionId` is asserted by the provider. An ACP session id, an ACPX * record id, and a provider-native session id are three different identities, @@ -261,7 +261,7 @@ export interface MaterializedLaunchRecord { * so rather than asserting a name nothing would resume, and the accepted turn * asserts the identity instead. * - * `instructions` is the prepared text itself, and it is retained beside its + * `instructions` is the prepared text itself, and it is saved beside its * digest — the execution's secret gate runs before this record persists, so * prepared text carrying a credential-shaped value never reaches the journal * and never reaches a native UI either. @@ -276,7 +276,7 @@ export interface PreparedLaunchRecord { instructionChannel: string; instructionReconciliation: InstructionReconciliation; /** - * Retained rather than derived from the provider, because a client-allocated + * Saved rather than derived from the provider, because a client-allocated * identity a reader cannot tell from a returned one is an identity a replay * could silently replace. */ @@ -296,7 +296,7 @@ export interface PreparedLaunchRecord { * The one turn this launch owes before the native UI can open, when it owes * one. Absent means no turn is owed and none may be taken. * - * Retained with the preparation so the plan is fixed before anything is + * Saved with the preparation so the plan is fixed before anything is * spent: a replay reads which prompt and which request id the first attempt * committed to, rather than choosing them again. */ diff --git a/packages/core/src/agent/session-request.ts b/packages/core/src/agent/session-request.ts index 2a316a950..7bc25a3b4 100644 --- a/packages/core/src/agent/session-request.ts +++ b/packages/core/src/agent/session-request.ts @@ -7,7 +7,7 @@ * pins it, `` itself composes it — and every one of those handlers can * replace the arguments it routes. A durable identity carried there would be a * durable identity any middleware could rewrite, which is how two sibling - * `` sites become one retained mapping. + * `` sites become one saved mapping. * * So the two travel apart. The **authored name** stays on the public chain, * where it is descriptive and a handler may observe or change it. The diff --git a/packages/core/src/component-failures.ts b/packages/core/src/component-failures.ts index 9db405ed2..69b98e10b 100644 --- a/packages/core/src/component-failures.ts +++ b/packages/core/src/component-failures.ts @@ -121,7 +121,7 @@ export function* refuseCheckedFailure(checkedFailures: CheckedFailures): Operati * * ```ts * export default printErrors(function* (props) { - * // body, requested content, retained work and teardown are all inside + * // body, requested content, saved work and teardown are all inside * }); * ``` * diff --git a/packages/core/src/components/Evaluate.ts b/packages/core/src/components/Evaluate.ts index 9052facdd..ad8b1aac4 100644 --- a/packages/core/src/components/Evaluate.ts +++ b/packages/core/src/components/Evaluate.ts @@ -62,7 +62,7 @@ * Evaluate is itself an ordinary text component here. Unbound it emits what the * fragment rendered; under `as` the engine binds that text and suppresses the * output, by the same rule that governs every other component. An admitted - * write puts nothing here — what it did is retained by its own ordinary durable + * write puts nothing here — what it did is saved by its own ordinary durable * effect, which is the authoritative account of it. * * It is deliberately not wrapped in `printErrors`. A refused or failed @@ -386,7 +386,7 @@ function selectedTables( * Every selected entry is an answer the import chain resolved, because that is * the one way a selected name reaches an implementation now. It travels with * the entry rather than being inferred at the record, so a reader comparing two - * admissions reads what each row was retained as. + * admissions reads what each row was saved as. */ function implementation(entry: CapturedEntry): GeneratedImplementation { return entry.kind === "component-answer" ? "component-answer" : "capability"; diff --git a/packages/core/src/components/Fetch.ts b/packages/core/src/components/Fetch.ts index e06cf4472..338a3e242 100644 --- a/packages/core/src/components/Fetch.ts +++ b/packages/core/src/components/Fetch.ts @@ -1,5 +1,5 @@ /** - * `` — one authorized HTTP read, retained + * `` — one authorized HTTP read, saved * (specs/executable-mdx-spec.md §6.18). * * ```md @@ -11,7 +11,7 @@ * transport is contextual `API.Fetch`, so a host that narrows reachable * destinations narrows this, and a test substitutes a provider at the same seam * a host uses. Nothing here reads a credential store, an environment variable, - * or a host token, and a header a document writes is ordinary retained data + * or a host token, and a header a document writes is ordinary saved data * rather than an authentication mechanism. * * The whole request is admitted before transport — the URL, the method, the @@ -87,7 +87,7 @@ function isSuccess(status: number): boolean { /** * The request and the body read, as one observation. * - * The headers are detached before the body is read, so what is retained does + * The headers are detached before the body is read, so what is saved does * not depend on a provider that invalidates its own header collection once the * body has been consumed. A `HEAD` never calls `text()`: there is no body to * read, and asking for one would fail against a provider that says so. @@ -96,7 +96,7 @@ function isSuccess(status: number): boolean { * admitted generated `` performs the request its entry's ceiling * admitted rather than the props it was written with, so it cannot enter this * component through its props — and a second observation of its own would be a - * second answer to what a retained response is. + * second answer to what a saved response is. */ export function* observeFetch(request: FetchRequest): Operation { const response = yield* fetch(request.url, { @@ -118,7 +118,7 @@ export default function* Fetch(props: Record): Operation { // written rather than to what came back. const bound = yield* hasBinding(); - const retained = yield* persistFetch( + const saved = yield* persistFetch( { id: expansion.id, ...(expansion.position === undefined ? {} : { position: expansion.position }), @@ -127,9 +127,9 @@ export default function* Fetch(props: Record): Operation { () => observeFetch(request), ); - const response = parseResponseRecord(retained); + const response = parseResponseRecord(saved); if (!bound && !isSuccess(response.status)) { throw new FetchStatusError(request.url, response.status); } - return retained; + return saved; } diff --git a/packages/core/src/components/Syntax.ts b/packages/core/src/components/Syntax.ts index 2d4f1e0f9..5f1d709b7 100644 --- a/packages/core/src/components/Syntax.ts +++ b/packages/core/src/components/Syntax.ts @@ -102,7 +102,7 @@ const NO_REFERENCE_REFUSAL = "nothing established what a document may write at this site."; const UNREADABLE_RECORD = - "the retained text is not a record this version can read, so no symbols were " + + "the saved text is not a record this version can read, so no symbols were " + "produced."; /** @@ -172,7 +172,7 @@ function syntax(claim: IdentityClaimant): ProtectedBody { throw new Error(NO_REFERENCE_REFUSAL); } const expansion = yield* getExpansion(); - // A refusal is *retained*, not signalled out of band. Core's own selection + // A refusal is *saved*, not signalled out of band. Core's own selection // refusal becomes a value the record distinguishes, so it is read back the // same way on a live run and on a replay; a provider that throws is not // caught here at all and fails the operation, which is what keeps it @@ -261,9 +261,9 @@ function* persistSymbols( throw new StaleInputError(UNREADABLE_RECORD); } if ("refused" in record) { - // Raised here rather than retained as an error, because a failure crossing + // Raised here rather than saved as an error, because a failure crossing // the durable boundary is rebuilt without its class and without any - // non-enumerable property. Re-raising from the retained *value* is what + // non-enumerable property. Re-raising from the saved *value* is what // makes a replayed refusal identical to a live one. throw markGeneratedRequestRefusal(new SyntaxSelectionRefusal(record.refused), record.refused); } diff --git a/packages/core/src/components/TempDir.ts b/packages/core/src/components/TempDir.ts index e58f93b26..a98f75f54 100644 --- a/packages/core/src/components/TempDir.ts +++ b/packages/core/src/components/TempDir.ts @@ -4,7 +4,7 @@ * Written with content it is a working directory: everything expanded inside * observes it as the contextual `Env.cwd`, and it is removed when the content * finishes, fails, or is cancelled. Written self-closing it is an allocation: - * the directory is retained at the invocation site so a later sibling can use + * the directory is saved at the invocation site so a later sibling can use * the path it renders, and removed with that scope. * * The directory comes from the installed `API.Files` provider, so this diff --git a/packages/core/src/components/component-resolution.ts b/packages/core/src/components/component-resolution.ts index b067d6377..30284f302 100644 --- a/packages/core/src/components/component-resolution.ts +++ b/packages/core/src/components/component-resolution.ts @@ -237,7 +237,7 @@ interface Answer { */ readonly baseline: ImportedDefinition | undefined; /** - * One retained copy per resolution this answer was published in. + * One saved copy per resolution this answer was published in. * * Window keys are weak: retaining an answer does not keep its past * resolutions alive. The outer record is weakly keyed by the answer, too. @@ -334,7 +334,7 @@ export interface OpenAnswerRequest { * * Holding this is the right to be asked, fixed to one origin. It is not the * right to answer: every publication goes through a request this mints for one - * invocation, so a retained installation handle can open a *new* request but + * invocation, so a saved installation handle can open a *new* request but * cannot revive a settled one or reach another provider's. */ export interface ProviderInstallation { @@ -472,7 +472,7 @@ export class CanonicalImports { * * Holding this is the right to be *asked*. It states nothing on its own: * every answer goes through a request this mints for one handler invocation, - * so a retained installation handle can be asked again — which a multi-name + * so a saved installation handle can be asked again — which a multi-name * provider needs — and can never revive a settled request or reach another * provider's. * @@ -561,7 +561,7 @@ export class CanonicalImports { // add: one import is one implementation, and this is that one again. return answer; } - // Retained once before it is compared: the answer may run user-controlled + // Saved once before it is compared: the answer may run user-controlled // machinery when read, so comparing it and then retaining it would leave // a gap in which those two reads could describe different definitions. // Asked before anything is recorded and before the window is spent, so a @@ -600,7 +600,7 @@ export class CanonicalImports { // Read off core's own copy rather than off the object the chain still // holds: a definition whose metadata answers one way for a check and // another way for the read would otherwise be validated as one thing and - // retained as another. An answer that could not be copied at all is left + // saved as another. An answer that could not be copied at all is left // for the comparison below to fail closed on, because there is nothing // here to validate. const refusal = baseline === undefined ? undefined : definitionGeneratedRefusal(baseline); diff --git a/packages/core/src/components/components.md b/packages/core/src/components/components.md index a63a87642..dee27fe40 100644 --- a/packages/core/src/components/components.md +++ b/packages/core/src/components/components.md @@ -62,7 +62,7 @@ or imply a permission they do not have. An empty `names` list, a duplicate name, a member that is not a string, and a name no symbol entry matches are each refused before anything is read, so a -refusal produces no partial text and no retained result. A paired spelling +refusal produces no partial text and no saved result. A paired spelling and any prop other than `names` and `as` are refused the same way. ### What it does not do @@ -143,7 +143,7 @@ answer is validated against, so what comes back is the shape the document said it needed rather than free text a later step has to interpret. The answer binds through `as`. -The question is asked once and retained. A run that resumes after the answer was +The question is asked once and saved. A run that resumes after the answer was given restores it rather than asking again, which is what makes a document with an elicitation in it safe to interrupt. diff --git a/packages/core/src/components/declared-markdown.ts b/packages/core/src/components/declared-markdown.ts index 1e1c3b8c6..59c90bcdb 100644 --- a/packages/core/src/components/declared-markdown.ts +++ b/packages/core/src/components/declared-markdown.ts @@ -421,7 +421,7 @@ export interface PrivateClosure { * * The offer is what admits the import, and it is spent where it was made. What may be * invoked is the object canonical core's own resolver produced *inside this - * ask* — so an answer retained from another import is admitted nowhere, however + * ask* — so an answer saved from another import is admitted nowhere, however * exactly it describes the same private component, and a name written where no * offer was made is not a private import at all. */ @@ -529,7 +529,7 @@ export class InstalledComponents implements ImportTier { * The closure the body of `imported` may write, if it is a declaration's own * body. * - * Decided from what canonical resolution retained — the definition core + * Decided from what canonical resolution saved — the definition core * invokes, reporting the origin core declared — rather than from the name * alone. A repository file, a bundled component and a registration all report * a different origin, and the answer core hands expansion is core's own copy diff --git a/packages/core/src/components/generated-metadata.ts b/packages/core/src/components/generated-metadata.ts index a92c88e48..00623251c 100644 --- a/packages/core/src/components/generated-metadata.ts +++ b/packages/core/src/components/generated-metadata.ts @@ -13,7 +13,7 @@ * case: metadata is what makes a form offerable, and offering none takes * nothing away from authored use. * - * ## Read once, validated and retained as one value + * ## Read once, validated and saved as one value * * A declaration crosses a package boundary, so the metadata is read the way * durable input is read: exactly the two members, an identity in the accepted @@ -101,7 +101,7 @@ export function admitGeneratedForms( return Err(new Error(`offers generated use of its ${form} form, which it does not accept`)); } // One read of each member, into data this module owns. Everything below — - // the checks and the retained copy alike — reads that data and never the + // the checks and the saved copy alike — reads that data and never the // declarer's object again. const offered = read(Reflect.get(generated, form), form); if (!offered.ok) { diff --git a/packages/core/src/components/protected.ts b/packages/core/src/components/protected.ts index 84df6637c..1c9b1840f 100644 --- a/packages/core/src/components/protected.ts +++ b/packages/core/src/components/protected.ts @@ -42,7 +42,7 @@ * win selection here. `Component.importComponent` middleware composes around the * import exactly as it composes around any other: it may observe it, delegate it * and refuse it by throwing, and what it cannot do is answer one — the answer is - * verified at the call site against core's own retained copy, and core's copy is + * verified at the call site against core's own saved copy, and core's copy is * what is invoked. * * Protection is not permission to do anything, and it is not a second diff --git a/packages/core/src/definition.ts b/packages/core/src/definition.ts index 945f0331f..7d1bc4a77 100644 --- a/packages/core/src/definition.ts +++ b/packages/core/src/definition.ts @@ -5,7 +5,7 @@ import { frontmatterFailure, parseFrontmatterPhased } from "./frontmatter.ts"; import type { FrontmatterPhase } from "./frontmatter.ts"; import { compilePropsSchema, compileReturnsSchema } from "./validate.ts"; import { scanComponentSpans, scanSegments } from "./scanner.ts"; -import { findTarget, outlineDocument, retainedRanges, selectTarget } from "./document-targets.ts"; +import { findTarget, outlineDocument, savedRanges, selectTarget } from "./document-targets.ts"; import type { DocumentOutline, DocumentTargetInfo } from "./document-targets.ts"; import matter from "gray-matter"; @@ -265,9 +265,9 @@ export interface ParsedRootDocument { * selector the whole body is scanned exactly as an ordinary markdown component * is. * - * A projection scans each retained range on its own, with the origin that range + * A projection scans each saved range on its own, with the origin that range * has in the original file, rather than scanning a concatenated string. Skipped - * source therefore cannot renumber what follows it: a retained element keeps + * source therefore cannot renumber what follows it: a saved element keeps * the offset and line it was authored at, and with them its expansion ID. */ export function* parseRootMarkdownDefinition( @@ -342,7 +342,7 @@ export function* parseRootMarkdownDefinitionPhased( const bodySegments: Segment[] = []; try { const newlines = newlineCounts(body.content); - for (const range of retainedRanges(outline, entry)) { + for (const range of savedRanges(outline, entry)) { bodySegments.push( ...scanSegments(body.content.slice(range.start, range.end), { path, @@ -362,7 +362,7 @@ export function* parseRootMarkdownDefinitionPhased( }); } -/** How many newlines precede each offset, so a retained range knows its line. */ +/** How many newlines precede each offset, so a saved range knows its line. */ function newlineCounts(body: string): number[] { const counts = new Array(body.length + 1); let seen = 0; diff --git a/packages/core/src/document-request.ts b/packages/core/src/document-request.ts index 309ded341..05d0a0765 100644 --- a/packages/core/src/document-request.ts +++ b/packages/core/src/document-request.ts @@ -327,13 +327,13 @@ export function issueDocument( * What a trusted host prepares inside the durable root. * * Captured by canonical core before any installation runs, and invoked after - * retained-history admission and before any public document policy or the root + * saved-history admission and before any public document policy or the root * import — so what it records precedes everything a handler could observe or * refuse. * * A `Workflow` rather than a plain `Operation`: preparation runs inside the * durable root, and what it prepares is meant to be journaled. A durable - * effect it completes is retained like any other, so a resumed run restores it + * effect it completes is saved like any other, so a resumed run restores it * instead of performing it again. */ export type DurablePreparation = () => Workflow; diff --git a/packages/core/src/document-targets.ts b/packages/core/src/document-targets.ts index c3d61aa9d..afc330590 100644 --- a/packages/core/src/document-targets.ts +++ b/packages/core/src/document-targets.ts @@ -17,7 +17,7 @@ * the mask changes what is *seen*, never where anything *is*. * * Projection retains original source ranges rather than a rebuilt document. Each - * retained range is scanned with its own origin, so every retained element keeps + * saved range is scanned with its own origin, so every saved element keeps * the offset and line it was authored at — which is what keeps expansion * identifiers equal between a full run and a targeted one. */ @@ -72,7 +72,7 @@ interface OutlineHeading { export interface DocumentOutline { readonly headings: readonly OutlineHeading[]; readonly entries: readonly DocumentTarget[]; - /** Canonical encoded fragments in source order, duplicates retained. */ + /** Canonical encoded fragments in source order, duplicates saved. */ readonly targets: readonly string[]; /** The same entries, in the same order, each with its own description. */ readonly targetInfo: readonly DocumentTargetInfo[]; @@ -110,7 +110,7 @@ const DOCUMENT_TARGET_FAILURE = "executablemd.document-target-failure"; * Why one requested selector did not name exactly one section, as data. * * Frozen and rebuilt from validated parts wherever it crosses a boundary. The - * selector is retained because reproducing an ordinary failed execution needs + * selector is saved because reproducing an ordinary failed execution needs * to say what was asked for — it is sanitized invocation metadata, never * identity, and it never stands in for an exact target. */ @@ -236,7 +236,7 @@ function consistentOutcome(fields: TargetFailureFields): boolean { * combination of kind, selector, matches and catalog that no selection could * have produced are all "not this shape" rather than a throw. * - * Nothing the candidate owns is retained. Every entry is read once and copied + * Nothing the candidate owns is saved. Every entry is read once and copied * into a fresh array, so a list that is mutated afterwards — or reached through * a Proxy that is later revoked — cannot change what comes back. */ @@ -946,7 +946,7 @@ function overlapsComponent(heading: RawHeading, spans: readonly ComponentSpan[]) * nearest preceding heading with a smaller depth — so skipped depths are * ordinary. "Outermost" is the smallest depth present, not `h1`. * - * A single outermost heading is the document's title: it is retained in every + * A single outermost heading is the document's title: it is saved in every * projection and takes no level in any path, which is why a document that opens * with one title still addresses its sections by their own names. */ @@ -1083,7 +1083,7 @@ function directPrefixEnd(outline: DocumentOutline, index: number): number { * gaps between them and are never scanned, which is what keeps their * components, resources, and code blocks from running at all. */ -export function retainedRanges( +export function savedRanges( outline: DocumentOutline, entry: DocumentTarget, ): readonly SourceRange[] { @@ -1108,14 +1108,14 @@ export function retainedRanges( end: subtreeEnd(outline, entry.heading), }); - const retained: SourceRange[] = []; + const saved: SourceRange[] = []; let consumed = 0; for (const range of ranges) { const start = Math.max(range.start, consumed); if (start < range.end) { - retained.push({ start, end: range.end }); + saved.push({ start, end: range.end }); consumed = range.end; } } - return retained; + return saved; } diff --git a/packages/core/src/document-validation.ts b/packages/core/src/document-validation.ts index 2422ea8a3..bd0b32865 100644 --- a/packages/core/src/document-validation.ts +++ b/packages/core/src/document-validation.ts @@ -456,7 +456,7 @@ class ValidationState { * * A path is bytes; what those bytes *mean* depends on who asked. Reading is * shared here so that a file reaches the walk once however many views of it - * the document needs, and so that a retained root's supplied text is what a + * the document needs, and so that a saved root's supplied text is what a * component selecting its path sees rather than whatever is on disk now. */ readonly #contents = new Map(); diff --git a/packages/core/src/documentation-index.ts b/packages/core/src/documentation-index.ts index 819bf7020..86cc2331c 100644 --- a/packages/core/src/documentation-index.ts +++ b/packages/core/src/documentation-index.ts @@ -99,7 +99,7 @@ export class DocumentationIndexError extends Error { * * Its own error because it is the author's mistake rather than the build's: a * misspelling, or a component that is not on this profile. It refuses the whole - * lookup, so nothing is rendered and nothing is retained. + * lookup, so nothing is rendered and nothing is saved. */ export class UnknownComponentError extends Error { override name = "UnknownComponentError"; diff --git a/packages/core/src/elicit-journal.ts b/packages/core/src/elicit-journal.ts index 668384c92..297bc2195 100644 --- a/packages/core/src/elicit-journal.ts +++ b/packages/core/src/elicit-journal.ts @@ -65,7 +65,7 @@ export interface ElicitationIdentity { /** `path:line:column` — the durable name. */ location: string; fingerprint: string; - /** The normalized schema, retained so history can show what was asked. */ + /** The normalized schema, saved so history can show what was asked. */ schema: JsonObject; /** Where the element was written, for history. Never read back as identity. */ position?: Readonly; diff --git a/packages/core/src/eval-handler.ts b/packages/core/src/eval-handler.ts index f09b7673d..45b6f6288 100644 --- a/packages/core/src/eval-handler.ts +++ b/packages/core/src/eval-handler.ts @@ -168,7 +168,7 @@ export const evalFactory: ModifierFactory = (_params) => (_args, _next) => if (persist) { // Persist mode: run the compiled block inside the eval scope - // so spawned resources are retained in the persistent EvalScope. + // so spawned resources are saved in the persistent EvalScope. const scope = yield* evalScope; if (!scope) { throw new Error( diff --git a/packages/core/src/evaluation-profile.ts b/packages/core/src/evaluation-profile.ts index 4cb779397..709623698 100644 --- a/packages/core/src/evaluation-profile.ts +++ b/packages/core/src/evaluation-profile.ts @@ -62,12 +62,12 @@ import type { FunctionComponent, FunctionComponentDefinition, Json, PropsSchema * * Produced by canonical execution's capture step before this module is asked to * capture anything: the chain has answered, the definition published on that - * exact final answer has been taken off it, and it has been retained + * exact final answer has been taken off it, and it has been saved * defensively. What arrives here is the settled result, so nothing in this * module resolves, looks up, or holds a resolver it could call later. */ export interface ResolvedAnswer { - /** Core's own retained copy, which is what a fragment invokes. */ + /** Core's own saved copy, which is what a fragment invokes. */ readonly definition: FunctionComponentDefinition; /** Which installation published it, as documentation provenance. */ readonly origin: string; @@ -115,7 +115,7 @@ export interface CapturedEntry { /** * What a fragment invokes. * - * Core's retained copy of the exact final answer the import chain gave, held + * Core's saved copy of the exact final answer the import chain gave, held * behind this execution's lifetime — never the object the chain was holding, * and never something re-resolved later. */ @@ -153,7 +153,7 @@ export interface EvaluationProfile { * End them. * * Called by canonical execution at teardown, and reachable from nowhere a - * document can put code: an operation a fragment or a handler retained past + * document can put code: an operation a fragment or a handler saved past * the execution refuses rather than acting on a filesystem the run no longer * holds a transaction for. */ @@ -207,7 +207,7 @@ export function evaluationLifetime(): EvaluationLifetime { * resolved and before public execution policy. * * Nothing is consulted here that a host still holds. The selection arrived as - * owned, validated data; each definition arrived as core's own retained copy of + * owned, validated data; each definition arrived as core's own saved copy of * an answer canonical resolution produced; and everything structural this * module derives — the props contract, the description, the admitted forms and * which table each of them lands in — is read off that definition rather than diff --git a/packages/core/src/execute.ts b/packages/core/src/execute.ts index abb63e3e8..d6c2705f7 100644 --- a/packages/core/src/execute.ts +++ b/packages/core/src/execute.ts @@ -131,7 +131,7 @@ import type { ExecutionDeclarationCatalog, ExpansionHandler, ExpansionRequest, - RetainedInstallation, + SavedInstallation, } from "./execution-declarations.ts"; import { documentationOf } from "./components/documentation.ts"; import { registerComponents } from "./components/registration.ts"; @@ -248,18 +248,18 @@ type DurableSelection = /** * A component the workflow definition is closed over. * - * The exact pinned source is retained, as a repository selection retains what + * The exact pinned source is saved, as a repository selection retains what * it read, so a replay reconstructs the component from the record instead of * resolving a name again. `path` is the canonical repository-relative path of * the blob, never the declaration a root document wrote, and the hash is the - * blob's own object id — which is what a trusted host verifies the retained + * blob's own object id — which is what a trusted host verifies the saved * selection against before any of it is replayed. */ | { kind: "workflow"; path: string; sourceHash: string; content: string } /** * Exact Markdown this execution declares. * - * The origin, the digest and the bytes are all retained, because what a + * The origin, the digest and the bytes are all saved, because what a * replay has to be sure of is that the run it is continuing was expanding * *this* asset. A build that ships different bytes under the same name, or a * host that no longer declares it, refuses rather than continuing somebody @@ -274,7 +274,7 @@ type DurableSelection = * Whether the host declared this component's rendering to be source. * * Closed: present only as `true`, and absent means the ordinary prose - * disposition. It is retained because it decides how the bytes this + * disposition. It is saved because it decides how the bytes this * component produces are published — a continuation that resumed under a * different answer would present a program as prose, or prose as a * program, from the same origin and the same digest. @@ -284,7 +284,7 @@ type DurableSelection = /** * A component only the declaration that carries it may write. * - * Nothing else is retained: the implementation is built from an identity + * Nothing else is saved: the implementation is built from an identity * authenticator this execution minted and is reachable from nowhere, so a * replay reconstructs it the same way the live run did — by being inside the * same declaration's body when it asks. @@ -293,7 +293,7 @@ type DurableSelection = /** * A component canonical core protects. * - * Nothing but the kind is retained. The name is already the record's identity, + * Nothing but the kind is saved. The name is already the record's identity, * and what the name resolves to is core's own table rather than anything a * host, a registry or a checkout supplies — so a replay reconstructs it the * way the live run did, by asking the copy of core that is running. @@ -779,7 +779,7 @@ function* resolveImportUnrecorded( const UNPUBLISHED_ANSWER = "a host selected a name for generated use, and the implementation the import chain answered " + "with is neither canonical resolution's own answer nor one a provider published. A fragment " + - "runs the implementation its execution retained, or it runs none."; + "runs the implementation its execution saved, or it runs none."; /** * Resolve every name this host selected, once, before public execution policy, @@ -1186,10 +1186,10 @@ function describeSelection(selection: SelectionOutcome): string { * the execution, reachable through no context and replaceable by nothing. * * It runs where a journal first becomes readable, so it is ahead of everything - * a wrong answer could reach: public guard policy, any retained Yield reaching - * execution, a retained Close being reused, authored work, and any append. + * a wrong answer could reach: public guard policy, any saved Yield reaching + * execution, a saved Close being reused, authored work, and any append. * - * It also owns the retained snapshot. The events it validates are the events it + * It also owns the saved snapshot. The events it validates are the events it * returns, so the identity and settlement it decided on are what every later * phase observes rather than a second reading of the backend's own objects. * @@ -1214,22 +1214,20 @@ function guardedJournal( // is a compile-time claim: without this an admission could splice, reorder // or empty the history in place, and every later admission, root-history // validation and the replay itself would consume what it left behind. - // The events themselves are the retained graph's own, already sealed. - const retained: readonly DurableEvent[] = Object.freeze( - retainEvents(yield* stream.readAll()), - ); + // The events themselves are the saved graph's own, already sealed. + const saved: readonly DurableEvent[] = Object.freeze(retainEvents(yield* stream.readAll())); // What the trusted host required of this history, on that exact snapshot, // in the order it was captured and stopping at the first refusal. Ahead of // root-history admission, ReplayGuard, terminal reuse, authored work and // any append. for (const admission of admissions) { - yield* admission(retained); + yield* admission(saved); } - admitRootHistory(retained, root, coroutineId); + admitRootHistory(saved, root, coroutineId); // The same objects the admissions were held to. `readAll` is declared // mutable by the protocol, so this is a fresh array over the identical // sealed events rather than a second reading of the backend. - return [...retained]; + return [...saved]; }, append: (event: DurableEvent) => stream.append(event), }; @@ -1240,18 +1238,18 @@ function guardedJournal( * Decide whether this run may replay the history it was handed. * * Synchronous and total over journal-provided values: every way the history can - * refuse to be read is the one fixed diagnostic, and the retained events it + * refuse to be read is the one fixed diagnostic, and the saved events it * reads are the ones the caller keeps. */ function admitRootHistory( - retained: readonly DurableEvent[], + saved: readonly DurableEvent[], root: RootDocumentSource, coroutineId: CoroutineId, ): void { const imports: Yield[] = []; let terminal = false; - for (const event of retained) { - // The retained history has already settled every discriminator, so one that + for (const event of saved) { + // The saved history has already settled every discriminator, so one that // still refuses is a history this run cannot describe — not an event to // skip past on the way to reusing a terminal result. const kind = attempt(() => event.type); @@ -1266,7 +1264,7 @@ function admitRootHistory( // describes a run that never happened, whichever coroutine the Close // claims to belong to. // - // Its result is recognized here as well as its coroutine. The retained + // Its result is recognized here as well as its coroutine. The saved // history has already settled both, and forcing them now is what makes a // refusal this run's fixed diagnostic rather than a failure surfacing // later, out of some other phase's hands. @@ -1299,7 +1297,7 @@ function admitRootHistory( // No import at all. The one history this describes rather than // contradicts is one whose terminal core wrote before importing anything, // and said so. - admitPreRootTerminal(retained, root, coroutineId); + admitPreRootTerminal(saved, root, coroutineId); } else if (imports.length !== 1 || owned.length !== 1) { throw new Error(UNREADABLE_ROOT_RECORD); } @@ -1549,11 +1547,11 @@ function preRootTerminal( * arbitrary recorded failure does not become reusable by having no import. */ function admitPreRootTerminal( - retained: readonly DurableEvent[], + saved: readonly DurableEvent[], root: RootDocumentSource, coroutineId: CoroutineId, ): void { - const closes = retained.filter((event) => attempt(() => event.type) === "close"); + const closes = saved.filter((event) => attempt(() => event.type) === "close"); if (closes.length !== 1) { throw new Error(UNREADABLE_ROOT_RECORD); } @@ -1575,7 +1573,7 @@ function admitPreRootTerminal( } } -/** Whether a retained event is recognizably the root import. */ +/** Whether a saved event is recognizably the root import. */ function isRootImport(event: DurableEvent): boolean { return ( attempt( @@ -1656,7 +1654,7 @@ function createExecFactory(retainProcessOutput: boolean): ModifierFactory { * Run one command and report what it settled to. * * The context is an argument rather than something read back from the scope: - * a bound command is executed against the context canonical core retained, so + * a bound command is executed against the context canonical core saved, so * public `codeBlock` middleware cannot change which command runs, weaken the * routing that keeps its channels off the reader's terminal, or decide whether * this execution is bound at all. `bound` is the same fact, held the same way — @@ -1712,8 +1710,8 @@ function* execTerminal( liveStderr = kept.boundStderr ?? ""; return { exitCode: execResult.exitCode, - ...(kept.retainedStdout === undefined ? {} : { stdout: kept.retainedStdout }), - ...(kept.retainedStderr === undefined ? {} : { stderr: kept.retainedStderr }), + ...(kept.savedStdout === undefined ? {} : { stdout: kept.savedStdout }), + ...(kept.savedStderr === undefined ? {} : { stderr: kept.savedStderr }), } as unknown as Json; }, )) as unknown as ProcessOutcome; @@ -1721,7 +1719,7 @@ function* execTerminal( return { // A capture's binding comes from the record when there is one and from // the region's own live buffer when there is not. That is what lets a - // journaled run resume: replay never runs the child, so the retained + // journaled run resume: replay never runs the child, so the saved // stdout — and only stdout, never stderr — is the binding. Forwarded // bytes reached the reader already and are never rendered again. output: captured ? (result.stdout ?? live) : "", @@ -2126,7 +2124,7 @@ function* documentWorkflow( // JavaScriptCore under Bun replace the object's prototype and drop the name, // while Deno keeps it as an own property. A record read back from a journal // can hold that name, because `JSON.parse` makes it an own property, so the - // same retained value would become a different environment on different + // same saved value would become a different environment on different // runtimes. for (const [name, value] of Object.entries(initialBindings)) { Object.defineProperty(rootEnv.values, name, { @@ -2158,7 +2156,7 @@ function* documentWorkflow( // The root binding environment is installed as scope-local middleware // around the entire loop so all segments share it. Resources spawned by - // `persist` blocks are retained in the eval scope until expansion + // `persist` blocks are saved in the eval scope until expansion // completes, then torn down. // No frame for the root document itself: every element the root's body holds // carries that document's path in its own source position, so two roots are @@ -2197,7 +2195,7 @@ function* documentWorkflow( // belongs to the result here, so a host that prints the result on its own // stdout shows a command's stdout on the stream it leaves free — that is // a decision about the host's streams, taken downstream of the per-exec - // boundary, and it does not change the channel already retained. + // boundary, and it does not change the channel already saved. return yield* runValueRoot( root, root.returns as ReturnsSchema, @@ -2290,7 +2288,7 @@ function* documentWorkflow( }); // The catch is outside the `yield*`, not inside the scope: the expansion's - // teardown — the invocation being dismantled, retained work, and whatever + // teardown — the invocation being dismantled, saved work, and whatever // aggregate the platform builds from a body failure and a teardown failure // together — finishes as this returns. Describing the failure any earlier // would describe an error whose account of itself is not complete yet. @@ -2423,7 +2421,7 @@ interface ComponentAssembly { * the bundle, the declarations, the protected tier and the identity domains are * established first, because resolving a selected name goes through all of them; * then the contextual selection is read and validated whole; then each selected - * name is resolved once and its implementation and metadata retained together. + * name is resolved once and its implementation and metadata saved together. * * A policy that runs afterwards may still modify or refuse its document * request, and its registry, import or selection replacement cannot replace or @@ -2439,7 +2437,7 @@ function* assembleComponents( options: ExecuteOptions, bundles: readonly WorkflowComponentBundle[], identityComponents: readonly IdentityComponent[], - installed: readonly RetainedInstallation[], + installed: readonly SavedInstallation[], documentation: readonly DocumentationContribution[], canonicalImports: CanonicalImports, lifetime: EvaluationLifetime, @@ -2847,7 +2845,7 @@ function* executeDocument( // expansion rather than through this operation's return value. *applyBoundModifiers([_modifiers, request], _next) { // The delegated array is inspectable data and nothing more: what - // runs is the chain the request retained. + // runs is the chain the request saved. yield* claimBoundExec(request, (authored, context) => { const chain = composeBoundExecChain(authored, context, registry, boundChain); return chain() as unknown as Operation; @@ -2998,14 +2996,14 @@ function* executeDocument( } /** - * What an installation requires of the retained history, decided inside the + * What an installation requires of the saved history, decided inside the * execution's own journal read. * - * Refusal-only: it throws or it returns. It is handed the exact retained + * Refusal-only: it throws or it returns. It is handed the exact saved * snapshot and hands nothing back, so it cannot substitute a history, and it * never receives a `next` to decline. */ -export type JournalAdmission = (retained: readonly DurableEvent[]) => Operation; +export type JournalAdmission = (saved: readonly DurableEvent[]) => Operation; /** * What a trusted host attaches to one execution. @@ -3017,7 +3015,7 @@ export type JournalAdmission = (retained: readonly DurableEvent[]) => Operation< */ export interface ExecutionInstallation { /** - * Retained-history requirements, copied into an execution-owned collection + * Saved-history requirements, copied into an execution-owned collection * that installed code cannot observe, add to or remove from. */ readonly admissions?: readonly JournalAdmission[]; @@ -3052,7 +3050,7 @@ export interface ExecutionInstallation { /** * What this installation records inside the durable root. * - * Invoked after retained-history admission and before any public document + * Invoked after saved-history admission and before any public document * policy or the root import. A handler cannot prevent it, and cannot observe * the run before it exists. */ @@ -3409,9 +3407,9 @@ function reconcile(document: Result | undefined, teardown: Error | undefin * identity and an effect decide which implementation a continuation admits, so * a reading taken later — or a nested object left live for the factory to be * handed — would be a grant this declaration never made. What the validator - * answers with is what is retained, and what the forms are built from. + * answers with is what is saved, and what the forms are built from. */ -function retainedIdentityComponent(component: IdentityComponent): IdentityComponent { +function savedIdentityComponent(component: IdentityComponent): IdentityComponent { const name = component.name; // Read before the metadata, so the forms the offer is held against are the // forms this execution accepts the component in. @@ -3438,11 +3436,11 @@ function retainedIdentityComponent(component: IdentityComponent): IdentityCompon * forms or its prose from inside `install()` has rewritten nothing. * * Capture reads the discriminant once and refuses an unknown kind before any - * other member is read. A known kind is retained with its arm's copied members, - * so the branch and the retained declaration cannot disagree if a later read + * other member is read. A known kind is saved with its arm's copied members, + * so the branch and the saved declaration cannot disagree if a later read * would return a different kind. */ -function retainedDeclaration(declaration: ExecutionDeclaration): ExecutionDeclaration { +function savedDeclaration(declaration: ExecutionDeclaration): ExecutionDeclaration { // Read once, before any other member of this value is read, and used for both // the branch and the copy. Everything below is a statement *about* one arm or // the other, so reading one of those from a value that named neither would be @@ -3458,9 +3456,9 @@ function retainedDeclaration(declaration: ExecutionDeclaration): ExecutionDeclar kind, name: declaration.name, origin: declaration.origin, - forms: retainedList(declaration.forms), + forms: savedList(declaration.forms), props: detachedSchema(declaration.props), - syntax: retainedList(declaration.syntax), + syntax: savedList(declaration.syntax), description: declaration.description, context: declaration.context, parent: declaration.parent, @@ -3477,7 +3475,7 @@ function retainedDeclaration(declaration: ExecutionDeclaration): ExecutionDeclar ...(declaration.returns === undefined ? {} : { returns: detachedSchema(declaration.returns) }), ...(declaration.privates === undefined ? {} - : { privates: Object.freeze([...declaration.privates].map(retainedIdentityComponent)) }), + : { privates: Object.freeze([...declaration.privates].map(savedIdentityComponent)) }), ...(declaration.exact === undefined ? {} : { exact: declaration.exact }), }); } @@ -3490,7 +3488,7 @@ function retainedDeclaration(declaration: ExecutionDeclaration): ExecutionDeclar * was written and is refused there as the malformed declaration it is, rather * than failing here as whatever the copy happened to throw. */ -function retainedList(value: readonly Member[]): readonly Member[] { +function savedList(value: readonly Member[]): readonly Member[] { return Array.isArray(value) ? Object.freeze([...value]) : value; } @@ -3584,7 +3582,7 @@ function* invoke( // its own values rather than over an array a host still holds. const identityComponents = Object.freeze( installations.flatMap((installation) => - [...(installation.components ?? [])].map(retainedIdentityComponent), + [...(installation.components ?? [])].map(savedIdentityComponent), ), ); @@ -3604,9 +3602,7 @@ function* invoke( const bound: ExpansionHandler | undefined = expand === undefined ? undefined : expand.bind(installation); return Object.freeze({ - declarations: Object.freeze( - [...(installation.declarations ?? [])].map(retainedDeclaration), - ), + declarations: Object.freeze([...(installation.declarations ?? [])].map(savedDeclaration)), ...(bound === undefined ? {} : { expand: bound }), }); }), @@ -3634,7 +3630,7 @@ function* invoke( // The lifetime every captured generated implementation is held to, opened // and armed before a single `install()` runs — so what a capture retains is // revoked however the execution ends, including when it is halted partway - // through assembly. A fragment body, a handler or a retained callback that + // through assembly. A fragment body, a handler or a saved callback that // reaches one afterwards refuses rather than acting on a filesystem the run // no longer holds a transaction for. const lifetime = evaluationLifetime(); @@ -3710,7 +3706,7 @@ function* invoke( // callbacks have assembled the host, and before the public policy below. A // policy that replaces a registration, imports a different body or supplies a // broader selection afterwards changes neither the vocabulary nor the - // implementations this retained. + // implementations this saved. const assembly = yield* assembleComponents( options, bundles, @@ -3777,7 +3773,7 @@ export function executeObserved( * Invocation input, and only that. It is not an `ExecutionInstallation`, a * Context, a component, a middleware answer, a document prop or a durable * record, because each of those is something a running document can name, reach - * or be asked about — and what an earlier execution retained is none of a + * or be asked about — and what an earlier execution saved is none of a * document's business to discover. It crosses once, as the third argument of one * call, and nothing published anywhere describes it afterwards. * @@ -3805,7 +3801,7 @@ export interface ExecutionInitialization { } /** - * Nothing retained. + * Nothing saved. * * Frozen because it is *shared* — every ordinary run reads this one object — and * for no other reason. A record a host actually supplies is copied and left @@ -3851,7 +3847,7 @@ function initialRootBindings( if (name === ROOT_PROPS_BINDING) { throw new Error( "an initial root binding may not be named `props`. Each execution's props namespace is " + - "the one its own root validated, so a retained value of that name is not something a " + + "the one its own root validated, so a saved value of that name is not something a " + "later execution inherits.", ); } diff --git a/packages/core/src/execution-declarations.ts b/packages/core/src/execution-declarations.ts index 140e32b7b..55c61f460 100644 --- a/packages/core/src/execution-declarations.ts +++ b/packages/core/src/execution-declarations.ts @@ -41,7 +41,7 @@ * ## What crosses to the handler * * Nothing here calls a handler. The declarations are admitted, the handler an - * installation supplied is retained beside them, and the entry that holds it + * installation supplied is saved beside them, and the entry that holds it * is reachable only by holding this catalog. There is no registry, registrar, * plugin object, contextual slot, token or authentication handle. */ @@ -191,7 +191,7 @@ export interface ExpansionRequest { export type ExpansionHandler = (request: ExpansionRequest) => Operation; /** What one installation contributed, read once at capture. */ -export interface RetainedInstallation { +export interface SavedInstallation { readonly declarations: readonly ExecutionDeclaration[]; readonly expand?: ExpansionHandler; } @@ -379,7 +379,7 @@ export function* admitExecutionDeclarations( export type IncompleteStructural = "declarations-without-handler" | "handler-without-declarations"; export function incompleteStructural( - installation: RetainedInstallation, + installation: SavedInstallation, ): IncompleteStructural | undefined { const declares = installation.declarations.some( (declaration) => declaration.kind === "structural", @@ -403,7 +403,7 @@ export function incompleteStructural( * expand, so it is refused here — before the root document is read. */ export function* admitInstalledDeclarations( - installations: readonly RetainedInstallation[], + installations: readonly SavedInstallation[], registry: ComponentRegistry, ): Operation { return yield* admit(installations, registry, true); @@ -417,7 +417,7 @@ export function* admitInstalledDeclarations( * questions only a whole set can answer come afterwards. */ function* admit( - installations: readonly RetainedInstallation[], + installations: readonly SavedInstallation[], registry: ComponentRegistry, /** Whether the caller is an execution, and so has handlers to be held to. */ installed: boolean, diff --git a/packages/core/src/execution-environment.ts b/packages/core/src/execution-environment.ts index 189b68944..dc2792f6e 100644 --- a/packages/core/src/execution-environment.ts +++ b/packages/core/src/execution-environment.ts @@ -50,7 +50,7 @@ export interface ExecutionEnvironment { * middleware has run. * * For a name this execution fixed, it verifies the answer that came back and - * returns the execution's own retained definition instead. An unrelated open + * returns the execution's own saved definition instead. An unrelated open * import keeps whatever the public chain answered. */ readonly componentResolution?: ComponentResolution; @@ -147,9 +147,9 @@ export interface ExecutionEnvironment { */ readonly evaluationProfile?: EvaluationProfile; /** - * Routes a selected definition to the implementation this execution retained. + * Routes a selected definition to the implementation this execution saved. * - * `` and `` run core's own retained bodies rather than a + * `` and `` run core's own saved bodies rather than a * repository file, a registration, or anything import middleware substituted. * Generated XMD receives a narrower route than the document does, and every * route becomes unusable at teardown, so an implementation kept past the run diff --git a/packages/core/src/expand.ts b/packages/core/src/expand.ts index b9f8b93a9..4918a7dc8 100644 --- a/packages/core/src/expand.ts +++ b/packages/core/src/expand.ts @@ -1491,7 +1491,7 @@ function* expandListSegments( { type: "error", // stderr was already displayed on its way past, so the - // diagnostic quotes it only when this run retained it — and + // diagnostic quotes it only when this run saved it — and // what it quotes is what reached this run's boundary (#441). message: codeResult.stderr ? `Command failed (exit ${codeResult.exitCode}): ${codeResult.stderr}` @@ -2615,7 +2615,7 @@ function* expandAll( scope.get(DurableContext) === undefined ? yield* all(children.map((child) => child())) : (yield* durableAll( - children.map((child) => (): Workflow => ephemeral(retained(child()))), + children.map((child) => (): Workflow => ephemeral(saved(child()))), )).map(readEmissions); for (const runs of rendered) { @@ -2639,15 +2639,15 @@ function* expandAll( } /** One child's runs, as its durable close retains them. */ -function retained(child: Operation): Operation { +function saved(child: Operation): Operation { return (function* (): Operation { return (yield* child).map((run) => ({ text: run.text, exact: run.exact })); })(); } -/** What a child's retained close is refused with when it will not read. */ +/** What a child's saved close is refused with when it will not read. */ const UNREADABLE_RUNS = - "a spawned child's retained close cannot be read as what it rendered, so this run cannot " + + "a spawned child's saved close cannot be read as what it rendered, so this run cannot " + "rebuild the document that child produced. A history is read whole or not at all."; /** @@ -2887,7 +2887,7 @@ function* expandComponent( // from. An open import — one no tier claims — never sets it, however its // answer describes itself. authorizedCanonically = true; - // Closed authorization answers with core's retained copy rather than the + // Closed authorization answers with core's saved copy rather than the // object the resolver recorded, and the copy is what this expansion // invokes — so the selection is recorded against it too. Only here: an // open execution's answer travelled through public middleware, and @@ -2963,7 +2963,7 @@ function* expandComponent( // What this definition's own body may write. A declaration carries its // private closure here; every other body clears the caller's private closure. - // Decided from the definition canonical resolution retained — the one this + // Decided from the definition canonical resolution saved — the one this // expansion is about to invoke, reporting the origin core declared — rather // than from the name alone. const bodyEnvironment = environmentForBody(environment, name, definition); diff --git a/packages/core/src/fetch-journal.ts b/packages/core/src/fetch-journal.ts index 89fe7e590..bf6d9cc61 100644 --- a/packages/core/src/fetch-journal.ts +++ b/packages/core/src/fetch-journal.ts @@ -6,7 +6,7 @@ * complete. Committing the status and headers first and failing on the body * would publish a success for a response nobody has. * - * The retained input is the normalized request itself rather than a digest of + * The saved input is the normalized request itself rather than a digest of * it, because that request is what a reader of the history needs in order to * know what was asked. Identity is the effect's `type` and `name` as always — * the name being this expansion's, which the same document derives again on diff --git a/packages/core/src/fetch-request.ts b/packages/core/src/fetch-request.ts index 0f084f625..bb269e2fb 100644 --- a/packages/core/src/fetch-request.ts +++ b/packages/core/src/fetch-request.ts @@ -62,7 +62,7 @@ export function requestRecord(request: FetchRequest): JsonObject { * Parsing answers whether the string is an absolute `http:` or `https:` URL; * it does not decide what the request is *for*. `URL.href` normalizes case, * default ports, and escaping, and sending a URL the author did not write would - * make the retained request disagree with the document. + * make the saved request disagree with the document. */ function admitUrl(value: Json): string { if (typeof value !== "string") { diff --git a/packages/core/src/fetch-response.ts b/packages/core/src/fetch-response.ts index 469567bff..4cdcdb2cf 100644 --- a/packages/core/src/fetch-response.ts +++ b/packages/core/src/fetch-response.ts @@ -43,21 +43,21 @@ export function responseRecord(response: FetchResponseRecord): JsonObject { }; } -/** Read a response back from what was retained, or from a replay. */ +/** Read a response back from what was saved, or from a replay. */ export function parseResponseRecord(value: Json): FetchResponseRecord { if (!isJsonObject(value)) { - throw new FetchResponseError("a retained Fetch response must be a JSON object"); + throw new FetchResponseError("a saved Fetch response must be a JSON object"); } const { status, headers, body } = value; if (typeof status !== "number" || !isJsonObject(headers) || typeof body !== "string") { throw new FetchResponseError( - "a retained Fetch response must carry a numeric status, header object and text body", + "a saved Fetch response must carry a numeric status, header object and text body", ); } const parsed: Record = {}; for (const [name, header] of Object.entries(headers)) { if (typeof header !== "string") { - throw new FetchResponseError(`retained Fetch response header "${name}" is not a string`); + throw new FetchResponseError(`saved Fetch response header "${name}" is not a string`); } parsed[name] = header; } @@ -86,7 +86,7 @@ export function detachHeaders(headers: ResponseHeaders): Record if (typeof entries !== "function") { throw new FetchResponseError( "the Fetch provider cannot enumerate response headers, so the complete response " + - "cannot be retained. A substituted provider supplies `headers.entries()`.", + "cannot be saved. A substituted provider supplies `headers.entries()`.", ); } const combined = new Map(); diff --git a/packages/core/src/foreground.ts b/packages/core/src/foreground.ts index 24b9bc536..5a967006f 100644 --- a/packages/core/src/foreground.ts +++ b/packages/core/src/foreground.ts @@ -101,9 +101,9 @@ export interface ForegroundOutput { /** stdout a `` region asked for; empty otherwise. */ captured: string; /** stdout the host asked to retain; undefined when it asked for none. */ - retainedStdout: string | undefined; + savedStdout: string | undefined; /** stderr the host asked to retain; undefined when it asked for none. */ - retainedStderr: string | undefined; + savedStderr: string | undefined; /** stdout a bound block's binding asked for; undefined when none did. */ boundStdout: string | undefined; /** stderr a bound block's binding asked for; undefined when none did. */ @@ -114,7 +114,7 @@ export interface ForegroundOutput { * Route one block's channels, and keep what this run has been told to keep. * * Installed before the child is acquired, so a chunk written during startup is - * routed and retained like any other. Routing and retention are decided + * routed and saved like any other. Routing and retention are decided * together because they read the same bytes and must not read them twice: * * - retention records first, so silencing a block never weakens a record the @@ -149,8 +149,8 @@ export function* route( bind = false, ): Operation<() => ForegroundOutput> { let captured = ""; - let retainedStdout = ""; - let retainedStderr = ""; + let savedStdout = ""; + let savedStderr = ""; let boundStdout = ""; let boundStderr = ""; const fromStdout = new TextDecoder(); @@ -164,7 +164,7 @@ export function* route( // stateful decoder twice would corrupt a split code point. const text = wantsStdout ? fromStdout.decode(bytes, { stream: true }) : ""; if (retain) { - retainedStdout += text; + savedStdout += text; } if (bind) { boundStdout += text; @@ -181,7 +181,7 @@ export function* route( *stderr([bytes], next) { const text = wantsStderr ? fromStderr.decode(bytes, { stream: true }) : ""; if (retain) { - retainedStderr += text; + savedStderr += text; } if (bind) { boundStderr += text; @@ -199,7 +199,7 @@ export function* route( if (wantsStdout) { const tail = fromStdout.decode(); if (retain) { - retainedStdout += tail; + savedStdout += tail; } if (bind) { boundStdout += tail; @@ -211,7 +211,7 @@ export function* route( if (wantsStderr) { const tail = fromStderr.decode(); if (retain) { - retainedStderr += tail; + savedStderr += tail; } if (bind) { boundStderr += tail; @@ -219,8 +219,8 @@ export function* route( } return { captured, - retainedStdout: retain ? retainedStdout : undefined, - retainedStderr: retain ? retainedStderr : undefined, + savedStdout: retain ? savedStdout : undefined, + savedStderr: retain ? savedStderr : undefined, boundStdout: bind ? boundStdout : undefined, boundStderr: bind ? boundStderr : undefined, }; diff --git a/packages/core/src/generated-request-refusal.ts b/packages/core/src/generated-request-refusal.ts index 3e55c5060..a4cd1a845 100644 --- a/packages/core/src/generated-request-refusal.ts +++ b/packages/core/src/generated-request-refusal.ts @@ -17,7 +17,7 @@ * * ## Why a tag rather than a class * - * `GeneratedXmdError` is raised for both a refused construct and a retained + * `GeneratedXmdError` is raised for both a refused construct and a saved * admission whose ceilings moved — the same class, opposite meanings — so * reading it by `instanceof` would call stale history and a revoked profile a * refusal of the request. Matching the message is worse: the sentences are fixed @@ -47,7 +47,7 @@ * * It is not how a *durable* refusal travels. A failure crossing a durable * boundary is rebuilt without its class and without any non-enumerable property, - * so a refusal that has to survive replay is retained as a value the record + * so a refusal that has to survive replay is saved as a value the record * distinguishes and re-raised — marked again — when that value is read back. * `components/Syntax.ts` is the one place that needs this today. */ diff --git a/packages/core/src/generated-selection.ts b/packages/core/src/generated-selection.ts index 8bac8ae24..08962c01c 100644 --- a/packages/core/src/generated-selection.ts +++ b/packages/core/src/generated-selection.ts @@ -17,7 +17,7 @@ * from it. Checking the host's object and then reading it again to retain it * would be two readings of a value the host controls, where an accessor could * answer one thing for the check and another for the copy. So the reading that - * was validated is the reading that is retained. + * was validated is the reading that is saved. * * ## Whole, or not at all * @@ -46,7 +46,7 @@ export class GeneratedSelectionError extends Error { * Sorted by component name rather than kept in the order a host's object * happened to enumerate in. Two middleware handlers compose one selection * normally, so enumeration order is a property of the assembly rather than of - * what the host chose — and the retained admission compares the selected rows + * what the host chose — and the saved admission compares the selected rows * position by position. Deriving the order from the names makes that comparison * a fact about the selection instead of about how it was built. */ @@ -86,7 +86,7 @@ export function admitGeneratedSelection( ); } // One read of this member, into data this module owns. The copy below is - // what every check and the retained map read; the host's object is never + // what every check and the saved map read; the host's object is never // consulted again. admitted.set(key, readForms(key, Reflect.get(selection, key))); } diff --git a/packages/core/src/generated-xmd.ts b/packages/core/src/generated-xmd.ts index a0d428602..d7407a5d6 100644 --- a/packages/core/src/generated-xmd.ts +++ b/packages/core/src/generated-xmd.ts @@ -24,8 +24,8 @@ * thrown out of it: a thrown one is serialized into the journal with its * message and its stack. * - * The line is admission. Before it, nothing of the candidate is retained, so a - * refusal carries none of it. After it the exact source is retained on purpose, + * The line is admission. Before it, nothing of the candidate is saved, so a + * refusal carries none of it. After it the exact source is saved on purpose, * so an ordinary expansion diagnostic quoting the fragment discloses nothing * the journal does not already hold. * @@ -65,9 +65,9 @@ * ## A resumed run is held to the selection it was admitted under * * Durable replay matches an effect by its type and name; what a description - * carries is stored, never compared. So the retained admission carries the + * carries is stored, never compared. So the saved admission carries the * normalized policy in its **result** as well as in the event input, and a - * continuation checks that retained policy against the one this run states + * continuation checks that saved policy against the one this run states * before a single generated component is invoked or a single effect is * performed. Every term compares whole and exactly: a changed class selection, * a changed identity behind the same name, and a changed admitted form are @@ -78,7 +78,7 @@ * What is deliberately absent is everything live. Which destination an admitted * `` may reach, and what timeout it resolves, are decided where the * request is performed — by the effective `Permissions` and `Config` of the - * scope that performs it — so a continuation held to a retained copy of either + * scope that performs it — so a continuation held to a saved copy of either * would refuse a run whose configuration legitimately moved. * * That is also why the walk lives inside the admission's live executor. A @@ -92,13 +92,13 @@ * exact source, the identity and form of each element the fragment named, and * the normalized policy; a refusal carries the construct class and nothing * else. Either way it commits before the first generated effect, and the - * effects themselves are retained by their own durable records — `fetch` for + * effects themselves are saved by their own durable records — `fetch` for * ``, the Workspace file effect for `` — so a replay restores both * without asking anyone anything a second time. * * An identity in that record is the URL the declaration that owns the * implementation offers that spelling under — `https://executable.md/components/file/read` - * for `` and `.../file/write` for `…` — retained beside + * for `` and `.../file/write` for `…` — saved beside * what kind of thing answered for the name and which effect table admitted it. * One component, one definition, one identity per form, all of them stated * where the implementation is. A record written by an earlier build holds a @@ -224,24 +224,24 @@ const CONSTRUCT: Record = { * refusal must not publish. */ const CEILING = - "a generated fragment was admitted under a selection this run no longer states. A retained " + + "a generated fragment was admitted under a selection this run no longer states. A saved " + "admission resumes only under the exact effect classes, identities and forms it was admitted " + "with."; -const UNREADABLE = "the retained generated-XMD admission record cannot be read as one."; +const UNREADABLE = "the saved generated-XMD admission record cannot be read as one."; /** * What a resumed run is refused with when the text it now offers is not the * text that was admitted. * - * The admission is a decision about one exact fragment. Expanding the retained + * The admission is a decision about one exact fragment. Expanding the saved * copy while the caller holds a different one would run something nobody * admitted *this* run for — the earlier behavior, which silently preferred the - * retained text and let a changed candidate pass unnoticed. Naming neither + * saved text and let a changed candidate pass unnoticed. Naming neither * fragment, because both are generated text. */ const STALE_TEXT = - "a generated fragment was admitted for text this run no longer offers. A retained admission " + + "a generated fragment was admitted for text this run no longer offers. A saved admission " + "resumes only for the exact text it was made about."; /** @@ -303,7 +303,7 @@ export type GeneratedEffectClass = "read" | "write"; * Whether canonical core supplies the body behind an admitted name, or the * ordinary import chain answered for it. * - * Retained beside the identity, because the two are different grants even under + * Saved beside the identity, because the two are different grants even under * one URL: the first cannot be composed around and the second was resolved * through middleware, so a record that confused them would compare a fragment's * admission equal to an admission it never had. @@ -314,20 +314,20 @@ export type GeneratedImplementation = "capability" | "component-answer"; const IMPLEMENTATIONS: readonly GeneratedImplementation[] = ["capability", "component-answer"]; /** - * Whether the row this run states is the one a retained row was admitted under. + * Whether the row this run states is the one a saved row was admitted under. * * Member by member: the URL the spelling runs under, the spelling itself, what * kind of thing answered for the name, and which effect table admitted it. A * changed URL, a form added, an implementation resolved another way and a read * moved into the write table are each a different grant. */ -function sameRow(retained: RetainedEntry, current: RetainedEntry): boolean { +function sameRow(saved: SavedEntry, current: SavedEntry): boolean { return ( - retained.name === current.name && - retained.form === current.form && - retained.identity === current.identity && - retained.kind === current.kind && - retained.effect === current.effect + saved.name === current.name && + saved.form === current.form && + saved.identity === current.identity && + saved.kind === current.kind && + saved.effect === current.effect ); } @@ -343,7 +343,7 @@ export type GeneratedComponentForm = "self-closing" | "paired" | "either"; /** The two forms an element is actually written in. */ type AuthoredForm = "self-closing" | "paired"; -/** Canonical order, everywhere a class or a form is compared or retained. */ +/** Canonical order, everywhere a class or a form is compared or saved. */ const EFFECT_CLASSES: readonly GeneratedEffectClass[] = ["read", "write"]; const AUTHORED_FORMS: readonly AuthoredForm[] = ["self-closing", "paired"]; @@ -431,7 +431,7 @@ export function pinnedFetch(): GeneratedObservation { * exists rather than a caller reaching for the definition itself and hoping. * The identity the admission retains is the one core's declaration offers that * spelling under, so a run that later admitted the paired form would be stating - * a different policy and a retained admission would refuse it. + * a different policy and a saved admission would refuse it. * * An admitted read invokes the ordinary `` component and therefore the * installed Files provider, which under a workflow run is the transaction-bound @@ -518,7 +518,7 @@ export function pinnedComponent( * first. * * A mutation contributes nothing to the evaluator's result. What it did is - * retained by its own ordinary durable effect, which is the authoritative + * saved by its own ordinary durable effect, which is the authoritative * account of it. */ export interface GeneratedMutation { @@ -566,7 +566,7 @@ export function pinnedFileWrite(): GeneratedMutation { * therefore the installed Files provider, which under a workflow run is the * transaction-bound one, so the removal crosses the run's ordinary effect * transaction rather than a path of the evaluator's own. What it did is - * retained by the `workspace_file` effect it publishes there, and the evaluator + * saved by the `workspace_file` effect it publishes there, and the evaluator * invents no receipt beside it: a deletion renders nothing unless the fragment * authors output around it. */ @@ -621,7 +621,7 @@ export interface GeneratedXmdRequest { * One admitted identity, normalized out of the class table it came from. * * A composition entry, a read entry and a mutation entry differ in what selects - * them; everything the preflight and the retained policy do with them is the + * them; everything the preflight and the saved policy do with them is the * same, and this is that shape. */ interface Entry { @@ -656,10 +656,10 @@ interface Entry { * * One row per name and form rather than per entry, because the identity and the * effect belong to the spelling: a reader comparing two admissions sees exactly - * which spelling of which name moved, and the table a row came from is retained + * which spelling of which name moved, and the table a row came from is saved * beside it rather than inferred from where it sits. */ -interface RetainedEntry { +interface SavedEntry { readonly name: string; readonly form: AuthoredForm; readonly identity: string; @@ -668,7 +668,7 @@ interface RetainedEntry { } /** One element the fragment actually named, as the run retains it. */ -interface RetainedInvocation { +interface SavedInvocation { readonly name: string; readonly identity: string; readonly form: AuthoredForm; @@ -690,15 +690,15 @@ interface Policy { * Every admitted spelling, in the order the selection resolved them: * composition first, then the read table, then the write table. */ - readonly allowed: readonly RetainedEntry[]; + readonly allowed: readonly SavedEntry[]; } /** The decision this run recorded, restored from its own durable record. */ -type RetainedAdmission = +type SavedAdmission = | { readonly decision: "admitted"; readonly source: string; - readonly named: readonly RetainedInvocation[]; + readonly named: readonly SavedInvocation[]; readonly policy: Policy; } | { readonly decision: "refused"; readonly construct: Construct }; @@ -948,7 +948,7 @@ function selectedEntries( const entries: Entry[] = []; // One kept reading per definition object, so a name admitted for two // spellings stays one component with one implementation. - const retained = new Map(); + const saved = new Map(); // Before either effect table and under every selection: composition is not a // class `allow` chooses between, so its entries are in the policy the same // way whatever the caller asked for, and in one canonical position. @@ -976,7 +976,7 @@ function selectedEntries( // own declaration does not accept, is refused before the admission record // and before any body, and a member that answers differently on a second // reading never gets one. - const admitted = admitOffered(observation.name, observation.definition, retained); + const admitted = admitOffered(observation.name, observation.definition, saved); // The spellings this component is offered to observe in, narrowed by the // host where it constrained the entry to the self-closing one. Derived // rather than assumed: a component offering one reading spelling admits @@ -1002,7 +1002,7 @@ function selectedEntries( ); } for (const mutation of mutations) { - const admitted = admitOffered(mutation.name, mutation.definition, retained); + const admitted = admitOffered(mutation.name, mutation.definition, saved); const forms = authoredForms(mutation.form); entries.push({ name: mutation.name, @@ -1056,9 +1056,9 @@ interface AdmittedDefinition { function admitOffered( name: string, definition: FunctionComponentDefinition, - retained: Map, + saved: Map, ): AdmittedDefinition { - const held = retained.get(definition); + const held = saved.get(definition); if (held !== undefined) { return held; } @@ -1088,7 +1088,7 @@ function admitOffered( }), generated, }); - retained.set(definition, admitted); + saved.set(definition, admitted); return admitted; } @@ -1163,7 +1163,7 @@ function offeredIdentities( * definitions: an import is asked for by name, so a name resolves to exactly * one implementation and the form chooses only which identity that * implementation ran as. Either ambiguity is a malformed host table, refused - * before anything is retained rather than settled by whichever entry was listed + * before anything is saved rather than settled by whichever entry was listed * first. */ function admitted(entries: readonly Entry[]): Map { @@ -1232,10 +1232,10 @@ function sameStrings(one: readonly string[], other: readonly string[]): boolean /** The selected rows this run states, in the order the selection resolved them. */ function currentPolicy(allow: readonly GeneratedEffectClass[], entries: readonly Entry[]): Policy { - const allowed: RetainedEntry[] = []; + const allowed: SavedEntry[] = []; for (const entry of entries) { // One row per admitted spelling, in canonical form order within the entry, - // so the retained table says which spelling of which name was admitted as + // so the saved table says which spelling of which name was admitted as // what rather than leaving the effect to be inferred from a position. for (const form of AUTHORED_FORMS.filter((candidate) => entry.forms.includes(candidate))) { const identity = entry.identities.get(form); @@ -1290,7 +1290,7 @@ function policyRecord(policy: Policy): JsonObject { /** * Whether an object carries exactly these members and nothing else. * - * Every retained shape below is closed, which is a stronger requirement than + * Every saved shape below is closed, which is a stronger requirement than * "the members it needs are present and well-typed". A record carrying an * extra member was written by something this build does not know the rules of, * and reading the members it recognizes would be admitting a grant on terms it @@ -1358,7 +1358,7 @@ function readClasses(value: Json): GeneratedEffectClass[] | undefined { } /** - * One retained identity: the URL string the spelling ran under. + * One saved identity: the URL string the spelling ran under. * * A string in the accepted namespace and nothing else. An object — the * structural identity an earlier build wrote — a relative reference, a URL @@ -1373,15 +1373,15 @@ function readIdentity(value: Json | undefined): string | undefined { } /** The effect table one row was admitted from. */ -function readEffect(value: Json | undefined): RetainedEntry["effect"] | undefined { +function readEffect(value: Json | undefined): SavedEntry["effect"] | undefined { return value === "composition" ? "composition" : EFFECT_CLASSES.find((known) => known === value); } -function readAllowed(value: Json): RetainedEntry[] | undefined { +function readAllowed(value: Json): SavedEntry[] | undefined { if (!Array.isArray(value)) { return undefined; } - const allowed: RetainedEntry[] = []; + const allowed: SavedEntry[] = []; for (const entry of value) { if (!isJsonObject(entry) || !exactly(entry, ["name", "form", "identity", "kind", "effect"])) { return undefined; @@ -1405,11 +1405,11 @@ function readAllowed(value: Json): RetainedEntry[] | undefined { return allowed; } -function readNamed(value: Json): RetainedInvocation[] | undefined { +function readNamed(value: Json): SavedInvocation[] | undefined { if (!Array.isArray(value)) { return undefined; } - const named: RetainedInvocation[] = []; + const named: SavedInvocation[] = []; for (const entry of value) { if (!isJsonObject(entry) || !exactly(entry, ["name", "identity", "form"])) { return undefined; @@ -1426,7 +1426,7 @@ function readNamed(value: Json): RetainedInvocation[] | undefined { } /** - * Whether a resumed run still holds the grant the retained admission was made + * Whether a resumed run still holds the grant the saved admission was made * under. * * Every term compares whole and exactly, in order: a widened class selection, @@ -1436,21 +1436,21 @@ function readNamed(value: Json): RetainedInvocation[] | undefined { * precisely the one that comparing the *fragment* against the *current* policy * would wave through. */ -function policyHolds(retained: Policy, current: Policy): boolean { - if (!sameStrings(retained.allow, current.allow)) { +function policyHolds(saved: Policy, current: Policy): boolean { + if (!sameStrings(saved.allow, current.allow)) { return false; } - if (retained.allowed.length !== current.allowed.length) { + if (saved.allowed.length !== current.allowed.length) { return false; } - return retained.allowed.every((entry, index) => { + return saved.allowed.every((entry, index) => { const here = current.allowed[index]; return here !== undefined && sameRow(entry, here); }); } /** One element the fragment named, and the entry preflight selected for it. */ -interface Planned extends RetainedInvocation { +interface Planned extends SavedInvocation { readonly entry: Entry; } @@ -2001,7 +2001,7 @@ function* persistAdmission( * and a record somebody else wrote is not an admission because it happens to * have the right keys. */ -function readAdmission(value: Json): RetainedAdmission | undefined { +function readAdmission(value: Json): SavedAdmission | undefined { if (!isJsonObject(value)) { return undefined; } @@ -2032,14 +2032,14 @@ function readAdmission(value: Json): RetainedAdmission | undefined { return undefined; } const invocations = readNamed(value.named); - const retained = readPolicy(value.policy); - if (invocations === undefined || retained === undefined) { + const saved = readPolicy(value.policy); + if (invocations === undefined || saved === undefined) { return undefined; } - return { decision, source, named: invocations, policy: retained }; + return { decision, source, named: invocations, policy: saved }; } -/** Whether a retained string names one of the construct classes this version has. */ +/** Whether a saved string names one of the construct classes this version has. */ function isConstruct(value: string): value is Construct { return Object.hasOwn(CONSTRUCT, value); } @@ -2175,7 +2175,7 @@ export function* evaluateProtectedGeneratedXmd( ); } // Before a single component is invoked or a single effect is performed: a - // retained admission is a grant whose selected spellings, identities, + // saved admission is a grant whose selected spellings, identities, // effects and classes must be stated exactly again. A run that moved any of // them is asking for a different grant. if (!policyHolds(decided.policy, policy)) { @@ -2184,12 +2184,12 @@ export function* evaluateProtectedGeneratedXmd( // And for the exact text, on the same terms as the selection. An admission is // a decision about one fragment; a caller now holding a different one is // asking for a decision that was never made, so it refuses here rather than - // quietly expanding the retained copy in its place. + // quietly expanding the saved copy in its place. if (decided.source !== request.source) { throw new GeneratedXmdError(STALE_TEXT); } - // The retained source is what expands, so a continuation runs exactly the + // The saved source is what expands, so a continuation runs exactly the // bytes this run admitted rather than a caller's copy of them. const restored = yield* preflight(request.id, decided.source, table); return yield* expand(request.id, restored.segments, restored.named, componentRouting, syntax); diff --git a/packages/core/src/inspect.ts b/packages/core/src/inspect.ts index 5b958b12a..0946426c0 100644 --- a/packages/core/src/inspect.ts +++ b/packages/core/src/inspect.ts @@ -71,7 +71,7 @@ export interface DocumentInfo { * Every target the document addresses, as canonical encoded fragments without * the document path or a leading `#`, in document order. * - * Duplicates are retained: two sections that canonicalize to the same path + * Duplicates are saved: two sections that canonicalize to the same path * are an ambiguity a caller can see rather than one a selector resolves * arbitrarily. */ diff --git a/packages/core/src/invocation-identity.ts b/packages/core/src/invocation-identity.ts index f41f055ea..2006abf8f 100644 --- a/packages/core/src/invocation-identity.ts +++ b/packages/core/src/invocation-identity.ts @@ -3,7 +3,7 @@ * (architecture.md, *Capability-backed invocation identity*). * * A component that names a durable operation after its own invocation is - * asserting ownership: the name decides which retained record a replay + * asserting ownership: the name decides which saved record a replay * restores, so two invocations arriving at one name each replay the other's * work, and an implementation running under somebody else's identity commits * against its own storage under their expansion. Code Rule 15 says such a @@ -290,7 +290,7 @@ interface ComponentRoutingInstallation extends ComponentRouting { function createComponentRouting(): ComponentRoutingInstallation { // One liveness flag for the execution's whole route and every route narrowed - // from it: teardown revokes the lot at once. A retained route still holds its + // from it: teardown revokes the lot at once. A saved route still holds its // WeakMap, so liveness rather than reachability is what invalidates it. let live = true; interface Entry { @@ -623,7 +623,7 @@ export function isFormDispatcher(fn: unknown): boolean { * - the issuance is still live, and its frame is the one running now, so an * invocation kept from a sibling or a finished element answers nothing here; * - canonical resolution selected *this* dispatcher for the import that led - * here, so a dispatcher a handler retained and answered with reaches no body. + * here, so a dispatcher a handler saved and answered with reaches no body. * * Built by whichever copy of core is performing the execution — canonical * definition construction calls this, never the component module — so a diff --git a/packages/core/src/invocation.ts b/packages/core/src/invocation.ts index dff283a68..d2611bf7a 100644 --- a/packages/core/src/invocation.ts +++ b/packages/core/src/invocation.ts @@ -65,7 +65,7 @@ function asError(cause: unknown): Error { * * 1. halt the content scope — projected content, to completion; * 2. halt the body — the author's own resources; - * 3. halt A — whatever `persist eval` and `daemon` retained. + * 3. halt A — whatever `persist eval` and `daemon` saved. * * Every stage is attempted even when an earlier one throws. Success, error and * cancellation all leave through it: the body's throw is caught at its task diff --git a/packages/core/src/modifiers.ts b/packages/core/src/modifiers.ts index dc0095725..ee9b55689 100644 --- a/packages/core/src/modifiers.ts +++ b/packages/core/src/modifiers.ts @@ -89,7 +89,7 @@ export interface BoundExecChain { * Run the built-in exec terminal against a context, as a bound command. * * Composition uses this rather than the registry's `exec` entry, so the - * command runs against the context canonical core retained rather than one + * command runs against the context canonical core saved rather than one * public `codeBlock` middleware could answer with. */ terminal(context: CodeBlockContext): CodeBlockWorkflow; @@ -195,7 +195,7 @@ export function composeModifierChain( /** * Compose the chain for one bound command (spec §3.6). * - * Everything here comes from what canonical core retained when it issued the + * Everything here comes from what canonical core saved when it issued the * request: the authored modifiers, the context the block was expanded with, and * the built-in middleware this execution installed. Nothing a handler delegated * takes part, so a chain that lost a word, gained one, or was handed a different diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts index db83c75bc..b9ca494f8 100644 --- a/packages/core/src/plugin.ts +++ b/packages/core/src/plugin.ts @@ -6,7 +6,7 @@ * * A Plugin is a plain structural value. It carries a name and, optionally, one * `install` operation. Everything else about it — what it declares, what it - * expands, what it requires of a retained history, what middleware it composes + * expands, what it requires of a saved history, what middleware it composes * — happens inside that call, in the scope the host installs it in. * * Structural rather than branded, because a Plugin is ordinarily loaded from a @@ -63,7 +63,7 @@ export interface PluginInstallation { readonly structural?: readonly Structural[]; /** How this Plugin expands the structural syntax it declared. */ expand?(request: ExpansionRequest): Operation; - /** What this Plugin requires of a retained history before it is replayed. */ + /** What this Plugin requires of a saved history before it is replayed. */ readonly admissions?: readonly JournalAdmission[]; } diff --git a/packages/core/src/protected-content.ts b/packages/core/src/protected-content.ts index d9c9798dd..4e0a7ac5e 100644 --- a/packages/core/src/protected-content.ts +++ b/packages/core/src/protected-content.ts @@ -52,7 +52,7 @@ export interface ProtectedContentLease { * End it. * * Called from the same `finally` that closes the invocation's issued - * permissions, so a callback a body retained — in a closure, on an object it + * permissions, so a callback a body saved — in a closure, on an object it * returned, in something it spawned — refuses rather than projecting content * of an element that has finished. */ diff --git a/packages/core/src/root-source.ts b/packages/core/src/root-source.ts index 139434b18..fe120cccd 100644 --- a/packages/core/src/root-source.ts +++ b/packages/core/src/root-source.ts @@ -36,32 +36,32 @@ export interface InlineRootDocument { * inside that object. Text and identity travel together, as they do for an * inline document, so nothing can execute one document while reporting another. * - * `retained` is what keeps supplied text from travelling under an identity + * `saved` is what keeps supplied text from travelling under an identity * nobody vouched for. An inline document is branded by its path — there is only - * one `` — but a retained document's path is an ordinary one, so without + * one `` — but a saved document's path is an ordinary one, so without * this member `{ path, source }` would type-check for any path at all and the * engine would report a document as having come from a file it never read. * Constructing one is therefore a deliberate act, the way `inlineSource()` is. */ -export interface RetainedRootDocument { +export interface SavedRootDocument { readonly path: string; readonly source: string; - readonly retained: true; + readonly saved: true; /** The requested target selector, still encoded and possibly a glob. */ readonly target?: string; } /** Where a root document's text comes from: a path, or supplied text. */ -export type RootDocumentSource = FileRootDocument | InlineRootDocument | RetainedRootDocument; +export type RootDocumentSource = FileRootDocument | InlineRootDocument | SavedRootDocument; /** Supplied text as a root document reported by the path it came from. */ -export function retainedSource( +export function savedSource( path: string, source: string, options?: { readonly target?: string }, -): RetainedRootDocument { +): SavedRootDocument { const target = options?.target; - return { path, source, retained: true, ...(target === undefined ? {} : { target }) }; + return { path, source, saved: true, ...(target === undefined ? {} : { target }) }; } /** Supplied text as a root document carrying the `` identity. */ diff --git a/packages/core/src/syntax-reference.ts b/packages/core/src/syntax-reference.ts index 73a9c33ed..51a0a7c45 100644 --- a/packages/core/src/syntax-reference.ts +++ b/packages/core/src/syntax-reference.ts @@ -117,7 +117,7 @@ export interface CapturedSymbolInputs { * nothing at all. * * Each ask builds afresh. Two authored occurrences are two references, which - * is what makes an occurrence's retained symbols its own rather than a copy of + * is what makes an occurrence's saved symbols its own rather than a copy of * whichever one ran first. */ export function rootSyntaxReference( @@ -271,7 +271,7 @@ export function select( const selected: SelectedEntry[] = []; // Walked in symbol order rather than request order, so two documents asking // for the same components in different orders render the same text — which is - // what makes one occurrence's retained result comparable with another's. + // what makes one occurrence's saved result comparable with another's. for (const category of authoring.categories) { for (const entry of category.entries) { // Components only. `names` is a component lookup under the current diff --git a/packages/core/src/syntax-refusal.ts b/packages/core/src/syntax-refusal.ts index e3edee522..44d4ec4d8 100644 --- a/packages/core/src/syntax-refusal.ts +++ b/packages/core/src/syntax-refusal.ts @@ -23,7 +23,7 @@ * * ## How the conclusion survives replay * - * Not as an error. The executor turns a recognized refusal into the retained + * Not as an error. The executor turns a recognized refusal into the saved * value `{ refused }`, and `components/Syntax.ts` re-raises it — marked — when * that value is read back, which happens after the durable operation returns on * a live run and on a replay alike. A refusal that could not be published is diff --git a/packages/core/tests/agent-components.test.ts b/packages/core/tests/agent-components.test.ts index f7c468eea..7ad0a1b1b 100644 --- a/packages/core/tests/agent-components.test.ts +++ b/packages/core/tests/agent-components.test.ts @@ -242,7 +242,7 @@ describe("Tier AC — agent components", () => { }); // The composition the whole component exists for, black box: a value bound by - // ``, rendered by ``, consumed and retained by ``. Import + // ``, rendered by ``, consumed and saved by ``. Import // graphs cannot prove authored syntax, so the exact bytes are pinned here. it("AC21: sends and retains exactly what rendered", function* () { const stub = createStubProvider(); diff --git a/packages/core/tests/agent-function-components.test.ts b/packages/core/tests/agent-function-components.test.ts index c9ef155e7..c742d3788 100644 --- a/packages/core/tests/agent-function-components.test.ts +++ b/packages/core/tests/agent-function-components.test.ts @@ -898,7 +898,7 @@ describe("Tier AF — Session configuration", () => { expect(trace.startedKeys).toEqual([["type", "agent", "session"]]); }); - it("AF29: a setting nobody authored is retained as nothing at all", function* () { + it("AF29: a setting nobody authored is saved as nothing at all", function* () { const { result, events } = yield* runDoc( ['', '', "", ""].join( "\n", @@ -1740,7 +1740,7 @@ describe("AFP — a publisher that never appends retains nothing", () => { // no journal holds. expect(result.ok).toBe(false); // And nothing durable describes the turn, so nothing can be presented as - // retained. + // saved. expect( events.filter((event) => event.type === "yield" && event.description.type === "agent_prompt"), ).toEqual([]); diff --git a/packages/core/tests/agent-session-launch.test.ts b/packages/core/tests/agent-session-launch.test.ts index 2e7806617..5fc006799 100644 --- a/packages/core/tests/agent-session-launch.test.ts +++ b/packages/core/tests/agent-session-launch.test.ts @@ -6,7 +6,7 @@ * controlled launcher, so what it asserts is what a document gets. The stub * goes through the real seam — it records each phase through * `AgentLaunchJournal` and asks the real `NativeLauncher` Api to run the - * child — so the ordering, the retained records and the refusals are the + * child — so the ordering, the saved records and the refusals are the * production ones, with only the provider's own product replaced. */ import { describe, it } from "@executablemd/test-support/bdd"; @@ -198,7 +198,7 @@ function createLaunchStub(overrides: Partial = {}): LaunchStub { message: stub.configurationRefused, }; } else if (configuration !== undefined) { - // What this provider put the conversation under, retained as + // What this provider put the conversation under, saved as // the one thing a launch record says about its settings. prepared.configuration = { ...(configuration.model === undefined ? {} : { model: configuration.model }), @@ -314,7 +314,7 @@ interface RunOptions { /** * Run in a directory the caller owns, rather than a fresh one. * - * A launch's durable identity and its retained cwd both name the document's + * A launch's durable identity and its saved cwd both name the document's * own directory, so a replay of an earlier attempt has to be the same * document — a second temp directory replays nothing and proves nothing. */ @@ -538,7 +538,7 @@ function preparedRecord(events: DurableEvent[]): Record { return prepared.value as Record; } -/** The failure class the retained preparation carries, if it carries one. */ +/** The failure class the saved preparation carries, if it carries one. */ function preparedFailure(events: DurableEvent[]): string | undefined { const failure = preparedRecord(events).failure; if (typeof failure !== "object" || failure === null || Array.isArray(failure)) { @@ -547,8 +547,8 @@ function preparedFailure(events: DurableEvent[]): string | undefined { return typeof failure.class === "string" ? failure.class : undefined; } -/** Which launch phases the journal retained, in order. */ -function retainedPhases(events: DurableEvent[]): string[] { +/** Which launch phases the journal saved, in order. */ +function savedPhases(events: DurableEvent[]): string[] { return launchRecords(events).map((entry) => entry.name.split("/").at(-1) ?? ""); } @@ -743,7 +743,7 @@ describe("Tier SL — native session launch", () => { expect(record.launcher).toBe("stub"); expect(record.sessionState).toBe("created"); expect(record.instructionChannel).toBe("stub.systemPrompt"); - // The body is retained exactly as it rendered, and the digest is of that + // The body is saved exactly as it rendered, and the digest is of that // exact text — not of a tidied version of it. expect(record.instructions).toBe("\nYou are the implementor.\n"); expect(record.instructionsDigest).toBe(digest("\nYou are the implementor.\n")); @@ -781,7 +781,7 @@ describe("Tier SL — native session launch", () => { "launched without releasing its ACP session", ); // Detach is durable before spawn, so a refusal there costs no child. - expect(retainedPhases(run.events)).toEqual(["prepared", "detached"]); + expect(savedPhases(run.events)).toEqual(["prepared", "detached"]); expect(run.stub.exits).toBe(0); expect(run.launcher.requests).toEqual([]); }); @@ -796,7 +796,7 @@ describe("Tier SL — native session launch", () => { expect(run.launcher.requests.length).toBe(0); // The refusal is the only phase: a replay resumes from what happened, not // from a launch nobody performed. - expect(retainedPhases(run.events)).toEqual(["prepared"]); + expect(savedPhases(run.events)).toEqual(["prepared"]); expect(preparedFailure(run.events)).toBe("unsupported-capability"); }); @@ -818,11 +818,11 @@ describe("Tier SL — native session launch", () => { expect(second.launcher.requests.length).toBe(0); }); - it("SL15: partial replay resumes the retained identity without preparing again", function* () { + it("SL15: partial replay resumes the saved identity without preparing again", function* () { const stream = new InMemoryStream(); const stub = createLaunchStub(); // One document, interrupted and then resumed. The launch's durable identity - // and its retained cwd both name this directory, so a second one would + // and its saved cwd both name this directory, so a second one would // replay nothing. const dir = path.join(os.tmpdir(), `xmd-sl-${randomUUID()}`); yield* ensure(() => rm(dir, { recursive: true, force: true })); @@ -831,7 +831,7 @@ describe("Tier SL — native session launch", () => { expect(interrupted.requests.length).toBe(1); expect(stub.preparations.length).toBe(1); expect(stub.detaches).toBe(1); - // The child never exited, so no exit phase was retained. + // The child never exited, so no exit phase was saved. const afterCrash = yield* stream.readAll(); const crashNames = launchRecords(afterCrash).map((entry) => entry.name); expect(crashNames.some((name) => name.endsWith("/prepared"))).toBe(true); @@ -844,7 +844,7 @@ describe("Tier SL — native session launch", () => { // session was created. expect(stub.preparations.length).toBe(1); expect(stub.detaches).toBe(1); - // The native UI reattached to the identity the first attempt retained. + // The native UI reattached to the identity the first attempt saved. expect(resumed.launcher.requests.length).toBe(1); expect(resumed.launcher.requests[0]?.command).toEqual(["stub-ui", "--resume", "native-abc"]); }); @@ -956,16 +956,16 @@ describe("Tier FS — the final public launch surface", () => { // And the forged identity is nowhere durable. expect(JSON.stringify(run.events)).not.toContain("invented"); // No later phase, no child, and the provider was never asked to prepare. - expect(retainedPhases(run.events)).toEqual(["prepared"]); + expect(savedPhases(run.events)).toEqual(["prepared"]); expect(run.launcher.requests).toEqual([]); expect(run.stub.preparations).toEqual([]); expect(run.stub.detaches).toBe(0); }); - it("FS2: middleware that delegates cannot replace what the provider retained", function* () { + it("FS2: middleware that delegates cannot replace what the provider saved", function* () { // The other half: the provider really did prepare, detach and exit, and // the handler then answers with something else. What the document settles - // on is the retained account, not the last value returned up the chain. + // on is the saved account, not the last value returned up the chain. const run = yield* runDoc(LAUNCH, { intercept: function* (request, next) { yield* next(request); @@ -981,7 +981,7 @@ describe("Tier FS — the final public launch surface", () => { expect(run.result.ok).toBe(true); expect(preparedRecord(run.events)?.nativeSessionId).toBe("native-abc"); expect(JSON.stringify(run.events)).not.toContain("invented"); - expect(retainedPhases(run.events)).toEqual(["prepared", "detached", "exited"]); + expect(savedPhases(run.events)).toEqual(["prepared", "detached", "exited"]); // The real launch happened exactly once. expect(run.stub.preparations).toHaveLength(1); expect(run.launcher.requests).toHaveLength(1); @@ -1068,8 +1068,8 @@ describe("Tier FS — the final public launch surface", () => { ]); expect(run.result.ok).toBe(false); expect(preparedFailure(run.events)).toBe("unsupported-capability"); - // Only the refusal is retained, and no phase, detach or process happened. - expect(retainedPhases(run.events)).toEqual(["prepared"]); + // Only the refusal is saved, and no phase, detach or process happened. + expect(savedPhases(run.events)).toEqual(["prepared"]); expect(run.stub.preparations).toEqual([]); expect(run.stub.detaches).toBe(0); expect(run.stub.exits).toBe(0); @@ -1115,7 +1115,7 @@ describe("Tier FS — the final public launch surface", () => { expect(run.stub.preparations).toHaveLength(1); expect(run.launcher.requests).toHaveLength(1); - expect(retainedPhases(run.events)).toEqual(["prepared", "detached", "exited"]); + expect(savedPhases(run.events)).toEqual(["prepared", "detached", "exited"]); }); it("FS8: core no longer publishes a launch journal to compose around", function* () { @@ -1144,7 +1144,7 @@ const BINDING: ExecutableBuildBindingV1 = { executableDigest: { algorithm: "sha256", value: "d".repeat(64) }, }; -/** The same build as retained data, so a parser case starts from unknown. */ +/** The same build as saved data, so a parser case starts from unknown. */ const BOUND: Record = { schema: "executable-build.v1", reportedVersion: "2.1.241 (Claude Code)", @@ -1173,11 +1173,11 @@ function prepared(overrides: Record = {}): Record { /** * Run one launch to completion and then replay it, proving the second run - * reaches no provider and starts no child whatever the first one retained. + * reaches no provider and starts no child whatever the first one saved. */ -function* replayIsCold(retained: Partial): Operation { +function* replayIsCold(saved: Partial): Operation { const stream = new InMemoryStream(); - const stub = createLaunchStub(retained); + const stub = createLaunchStub(saved); const first = yield* runDoc(LAUNCH, { stream, stub }); expect(first.result.ok ? "" : first.result.error.message).toBe(""); expect(stub.factoryActivations).toBe(1); @@ -1263,7 +1263,7 @@ describe("Tier PV — identity provenance", () => { * A provider-native identity means one thing only while the build that issued * or accepted it can be recognized later, so the binding is exact on the way in * and exact on the way out. Core observes no executables: it reads what was - * retained and says whether two of them name the same build. + * saved and says whether two of them name the same build. */ describe("Tier EB — executable build binding", () => { function bound(overrides: Record = {}): Record { @@ -1321,7 +1321,7 @@ describe("Tier EB — executable build binding", () => { } }); - it("EB4: equality is over the retained build, and a path is not part of it", function* () { + it("EB4: equality is over the saved build, and a path is not part of it", function* () { // There is no path to ignore, which is the point: the same build reached // through a different path compares equal because nothing about where it // was ever entered the record. @@ -1367,7 +1367,7 @@ describe("Tier EB — executable build binding", () => { }); /** One durable yield, as the journal holds it. */ -interface Retained { +interface Saved { type: string; name: string; /** The description's `input`, which is what the effect was asked to do. */ @@ -1375,7 +1375,7 @@ interface Retained { value: Json | undefined; } -function retained(events: DurableEvent[]): Retained[] { +function saved(events: DurableEvent[]): Saved[] { return events.flatMap((event) => event.type === "yield" && event.result.status === "ok" ? [ @@ -1391,22 +1391,22 @@ function retained(events: DurableEvent[]): Retained[] { } function one(events: DurableEvent[], suffix: string): Record { - const found = retained(events).filter((entry) => entry.name.endsWith(suffix)); + const found = saved(events).filter((entry) => entry.name.endsWith(suffix)); if (found.length !== 1) { - throw new Error(`expected exactly one retained "${suffix}", found ${found.length}`); + throw new Error(`expected exactly one saved "${suffix}", found ${found.length}`); } const value = found[0]?.value; if (typeof value !== "object" || value === null || Array.isArray(value)) { - throw new Error(`retained "${suffix}" is not a record`); + throw new Error(`saved "${suffix}" is not a record`); } return value; } -/** One member of a retained record that is itself a record. */ +/** One member of a saved record that is itself a record. */ function nested(record: Record, member: string): Record { const value = record[member]; if (typeof value !== "object" || value === null || Array.isArray(value)) { - throw new Error(`retained member "${member}" is not a record`); + throw new Error(`saved member "${member}" is not a record`); } return value; } @@ -1432,7 +1432,7 @@ function* cutAfter(stream: InMemoryStream, suffix: string): Operation { return createLaunchStub({ materialization: OWED, ...overrides }); } - it("MR1: the owed turn is spent once and retained as both a prompt and a phase", function* () { + it("MR1: the owed turn is spent once and saved as both a prompt and a phase", function* () { const stub = owing(); const run = yield* runDoc(LAUNCH, { stub }); expect(run.result.ok ? "" : run.result.error.message).toBe(""); expect(stub.turns).toEqual([OWED.requestId]); - expect(retainedPhases(run.events)).toEqual(["prepared", "materialized", "detached", "exited"]); + expect(savedPhases(run.events)).toEqual(["prepared", "materialized", "detached", "exited"]); - // The plan is retained with the preparation, before anything is spent. + // The plan is saved with the preparation, before anything is spent. expect(one(run.events, "/prepared").materialization).toEqual({ promptVersion: OWED.promptVersion, requestId: OWED.requestId, prompt: OWED.prompt, }); // The prompt says the turn was spent, and says which turn it was. - const prompt = retained(run.events).find((entry) => entry.name.endsWith("/materialization")); + const prompt = saved(run.events).find((entry) => entry.name.endsWith("/materialization")); expect(prompt?.type).toBe("agent_prompt"); expect(prompt?.input).toBe(OWED.prompt); expect(one(run.events, "/materialization").status).toBe("completed"); @@ -1495,7 +1495,7 @@ describe("Tier MR — retaining and replaying the materialization turn", () => { expect(second.launcher.requests.length).toBe(0); }); - it("MR3: a retained prompt with no phase rebuilds the phase and spends nothing", function* () { + it("MR3: a saved prompt with no phase rebuilds the phase and spends nothing", function* () { const stub = owing(); const dir = path.join(os.tmpdir(), `xmd-mr-${randomUUID()}`); yield* ensure(() => rm(dir, { recursive: true, force: true })); @@ -1523,7 +1523,7 @@ describe("Tier MR — retaining and replaying the materialization turn", () => { expect(rebuilt.response).toBe("Acknowledged."); expect(rebuilt.usage).toEqual({}); expect(rebuilt.failure).toBe(undefined); - // And the launch goes on to hand the retained identity over. + // And the launch goes on to hand the saved identity over. expect(resumed.launcher.requests[0]?.command).toEqual(["stub-ui", "--resume", "native-abc"]); }); @@ -1548,10 +1548,10 @@ describe("Tier MR — retaining and replaying the materialization turn", () => { const refusal = nested(one(resumed.events, "/materialized"), "failure"); expect(refusal.class).toBe("session-recovery-required"); expect(refusal.message).toContain("not knowable"); - expect(retainedPhases(resumed.events)).toEqual(["prepared", "materialized"]); + expect(savedPhases(resumed.events)).toEqual(["prepared", "materialized"]); }); - it("MR5: a retained refusal replays as itself and never opens a native UI", function* () { + it("MR5: a saved refusal replays as itself and never opens a native UI", function* () { const stream = new InMemoryStream(); const stub = owing({ turnRefused: true }); const first = yield* runDoc(LAUNCH, { stream, stub }); @@ -1575,8 +1575,8 @@ describe("Tier MR — retaining and replaying the materialization turn", () => { expect(run.result.ok ? "" : run.result.error.message).toBe(""); expect(stub.turns).toEqual([]); expect(one(run.events, "/prepared").materialization).toBe(undefined); - expect(retainedPhases(run.events)).toEqual(["prepared", "detached", "exited"]); - expect(retained(run.events).some((entry) => entry.type === "agent_prompt")).toBe(false); + expect(savedPhases(run.events)).toEqual(["prepared", "detached", "exited"]); + expect(saved(run.events).some((entry) => entry.type === "agent_prompt")).toBe(false); }); it("MR7: a planned turn the provider cannot spend stops the launch", function* () { @@ -1610,10 +1610,10 @@ describe("Tier MR — retaining and replaying the materialization turn", () => { expect(run.result.ok ? "" : run.result.error.message).toContain( "without asserting the provider-native identity", ); - // The turn was spent and is retained as spent — what is missing is the + // The turn was spent and is saved as spent — what is missing is the // session, and there is nothing for a native UI to be handed. expect(stub.turns).toEqual([OWED.requestId]); - expect(retainedPhases(run.events)).toEqual(["prepared", "materialized"]); + expect(savedPhases(run.events)).toEqual(["prepared", "materialized"]); expect(stub.detaches).toBe(0); expect(run.launcher.requests.length).toBe(0); }); @@ -1709,7 +1709,7 @@ describe("Tier MJ — reading the materialization phase back", () => { for (const value of ["", 3, null, []]) { expect(parseMaterialized(materialized({ usage: value }))).toBe(undefined); } - // Reported nothing is a report, and it is retained as exactly that. + // Reported nothing is a report, and it is saved as exactly that. expect(parseMaterialized(materialized({ usage: {} }))?.usage).toEqual({}); }); @@ -1756,7 +1756,7 @@ const CONFIGURED_LAUNCH = [ ].join("\n"); describe("Tier SC — configured native launch", () => { - it("SC1: the routed use and the retained preparation carry the same two values", function* () { + it("SC1: the routed use and the saved preparation carry the same two values", function* () { const run = yield* runDoc(CONFIGURED_LAUNCH, { declareSession: true }); expect(run.result.ok ? "" : run.result.error.message).toBe(""); @@ -1778,9 +1778,9 @@ describe("Tier SC — configured native launch", () => { expect(run.result.ok ? "" : run.result.error.message).toContain( "whose configuration is not what this launch asked for", ); - // The preparation was retained — it is what happened — and nothing after it + // The preparation was saved — it is what happened — and nothing after it // was: no detach, and no native UI. - expect(retainedPhases(run.events)).toEqual(["prepared"]); + expect(savedPhases(run.events)).toEqual(["prepared"]); expect(run.stub.detaches).toBe(0); expect(run.launcher.requests.length).toBe(0); }); @@ -1835,7 +1835,7 @@ describe("Tier SC — the final routed Session decides a launch", () => { }); expect(run.result.ok ? "" : run.result.error.message).toBe(""); - // The launch happened, and nothing was configured or retained: a raw + // The launch happened, and nothing was configured or saved: a raw // session carries no settings, and the record says only what ran. expect(run.stub.configurations).toEqual([]); expect(preparedRecord(run.events).configuration).toBe(undefined); @@ -1901,7 +1901,7 @@ describe("Tier SC — the final routed Session decides a launch", () => { * feature existed still parses, and one whose member does not read back is * refused rather than replayed as a launch that asked for nothing. */ -describe("Tier SJ — configuration in a retained preparation", () => { +describe("Tier SJ — configuration in a saved preparation", () => { it("SJ1: a record without any of them parses as a launch that asked for nothing", function* () { expect(parsePrepared(prepared())?.configuration).toBe(undefined); }); @@ -1966,7 +1966,7 @@ describe("Tier SJ — configuration in a retained preparation", () => { } }); - it("SJ4: configuration-refused is a retained failure class", function* () { + it("SJ4: configuration-refused is a saved failure class", function* () { const parsed = parsePrepared( prepared({ failure: { class: "configuration-refused", message: "why" } }), ); @@ -1977,8 +1977,8 @@ describe("Tier SJ — configuration in a retained preparation", () => { /** * Tier SR — replaying a configured launch (issue #828). * - * What a launch asked its conversation to run under is retained, so a resumed - * launch reads it rather than deciding again — and the phases it retained stay + * What a launch asked its conversation to run under is saved, so a resumed + * launch reads it rather than deciding again — and the phases it saved stay * the phases that happened. Applying the configuration is the provider's; what * these rows own is the durable sequence around it. */ @@ -1993,10 +1993,10 @@ describe("Tier SR — configured launch replay", () => { expect(run.result.ok).toBe(false); expect(run.result.ok ? "" : run.result.error.message).toContain("Unknown model"); - // Retained as what happened, under the class an author can act on, and + // Saved as what happened, under the class an author can act on, and // nothing after it was authored. expect(preparedFailure(run.events)).toBe("configuration-refused"); - expect(retainedPhases(run.events)).toEqual(["prepared"]); + expect(savedPhases(run.events)).toEqual(["prepared"]); expect(run.stub.detaches).toBe(0); expect(run.launcher.requests.length).toBe(0); // A refusal names no settings: this conversation was never put under any, @@ -2027,7 +2027,7 @@ describe("Tier SR — configured launch replay", () => { // Neither phase ran again, and the journal still holds exactly one of each. expect(stub.preparations.length).toBe(1); expect(stub.detaches).toBe(1); - expect(retainedPhases(resumed.events).filter((phase) => phase === "detached").length).toBe(1); + expect(savedPhases(resumed.events).filter((phase) => phase === "detached").length).toBe(1); // What it was prepared with survived the interruption, which is what a // provider reapplies before it continues. expect(preparedRecord(resumed.events).configuration).toEqual({ diff --git a/packages/core/tests/all.test.ts b/packages/core/tests/all.test.ts index 9a2248cfb..8f9cbf292 100644 --- a/packages/core/tests/all.test.ts +++ b/packages/core/tests/all.test.ts @@ -39,7 +39,7 @@ import { forEach } from "@effectionx/stream-helpers"; import { collect } from "../src/collect.ts"; import { Component, retain } from "../src/component-api.ts"; import { getExpansion } from "../src/expansion.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { registerComponents } from "../src/components/registration.ts"; import { Agent } from "../src/agent/agent-api.ts"; import type { AgentPromptEvent } from "../src/agent/agent-api.ts"; @@ -123,9 +123,9 @@ class Harness { readonly ran: string[] = []; /** The expansion identifier each `` saw, by its name. */ readonly identities = new Map(); - /** `start:`/`stop:` marks the retained resources published. */ + /** `start:`/`stop:` marks the saved resources published. */ readonly timeline: string[] = []; - /** What each `` saw of a retained resource. */ + /** What each `` saw of a saved resource. */ readonly observed: string[] = []; readonly #arrivals = new Map(); @@ -803,7 +803,7 @@ const ISOLATION_DOC = [ describe("Tier ALL — PA4: a child's bindings and resources are its own", () => { beforeAll(() => useTempFileCompiler()); - it("ALL7: a binding and a retained resource live on in their child and nowhere else", function* () { + it("ALL7: a binding and a saved resource live on in their child and nowhere else", function* () { const shared = harness(); const result = yield* run(ISOLATION_DOC, { shared }); @@ -994,7 +994,7 @@ describe("Tier ALL — PA6: a failure cancels its siblings and publishes nothing expect(outcome).toBe("cancelled"); // The scope exited while both children were held, and both were joined: - // the retained resource is released and no gate recorded work. + // the saved resource is released and no gate recorded work. expect(shared.timeline).toEqual(["start:kept", "stop:kept"]); expect(gateOrder(stream.snapshot())).toEqual([]); }); @@ -1276,7 +1276,7 @@ function evaluateFragment(source: string): Operation { }; const installation: ExecutionInstallation = { prepare }; const execution = yield* executeInstalled( - { ...retainedSource(GENERATED_ROOT, GENERATED_SOURCE), stream, includes: [] }, + { ...savedSource(GENERATED_ROOT, GENERATED_SOURCE), stream, includes: [] }, [installation], ); const result = yield* execution; diff --git a/packages/core/tests/answer-identity.test.ts b/packages/core/tests/answer-identity.test.ts index 9e29f2e5e..815af3499 100644 --- a/packages/core/tests/answer-identity.test.ts +++ b/packages/core/tests/answer-identity.test.ts @@ -131,7 +131,7 @@ function answerWith(generated: unknown): ImportedDefinition { return Object.assign(bare(), { generated }); } -/** The generated identity on a retained definition, whichever arm it is. */ +/** The generated identity on a saved definition, whichever arm it is. */ function offeredIdentity(definition: ImportedDefinition | undefined): unknown { const generated = definition === undefined ? undefined : Reflect.get(definition, "generated"); if (typeof generated !== "object" || generated === null) { @@ -495,7 +495,7 @@ describe("Tier CIV — a publication belongs to one answer of one import", () => expect(imports.identify(resolution, published)).toBe(undefined); }); - it("CIV25: an installation retained past its execution opens nothing", function* () { + it("CIV25: an installation saved past its execution opens nothing", function* () { const imports = owner(); const provider = imports.provider(ORIGIN); const { resolution, before } = resolving(imports, "Open", (resolution) => ({ @@ -657,7 +657,7 @@ describe("Tier CIV — a publication belongs to one answer of one import", () => * * Each request retains the window that was open when its handler began. A * descendant scope preserves that correlation, and closing the window makes - * the retained request unable to record an answer’s identity in a later one. + * the saved request unable to record an answer’s identity in a later one. */ describe("Tier PA10b — one serial resolution window", () => { /** @@ -803,7 +803,7 @@ describe("Tier PA10c — one definition answering more than one import", () => { resolving(imports, "Open", (resolution) => { asking(provider, "Open", (request) => { // Refused before anything is recorded: the publication was about the - // definition core retained, and this object is no longer it. + // definition core saved, and this object is no longer it. expect(refusalOf(() => request.answer(shared))).toBeInstanceOf(AnswerIdentityError); return undefined; }); diff --git a/packages/core/tests/declared-markdown-component.test.ts b/packages/core/tests/declared-markdown-component.test.ts index f05d6d170..b1fa01dd9 100644 --- a/packages/core/tests/declared-markdown-component.test.ts +++ b/packages/core/tests/declared-markdown-component.test.ts @@ -59,7 +59,7 @@ import { installedBundle } from "../src/components/bundle.ts"; import { inspectComponent, inspectSyntax } from "../src/inspect.ts"; import { validateDocument, validateDocumentStructure } from "../src/document-validation.ts"; import { registerComponents } from "../src/components/registration.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { DocumentOutput } from "../src/api.ts"; import { useNormalizedOutput } from "../src/output/normalize.ts"; import { useTerminalOutput } from "../src/output/terminal.ts"; @@ -248,7 +248,7 @@ function run( return scoped(function* () { return yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, source), stream, includes: [...includes] }, + { ...savedSource(ROOT_PATH, source), stream, includes: [...includes] }, [installation(declarations), ...extra], ), ); @@ -265,7 +265,7 @@ function* refusal(operation: Operation): Operation { throw new Error("expected the operation to be refused"); } -/** Whether one retained event is a component import. */ +/** Whether one saved event is a component import. */ function isImport(event: DurableEvent): boolean { return event.type === "yield" && event.description.type === "import_component"; } @@ -857,7 +857,7 @@ describe("Tier DM — the private closure is lexical", () => { } if (kept === undefined) { // Inside the declaration: an ordinary delegated import, - // and the answer is retained rather than altered. + // and the answer is saved rather than altered. kept = yield* next(name, position); return kept as never; } @@ -915,7 +915,7 @@ describe("Tier DM — the private closure is lexical", () => { } const validation = yield* validateDocument({ - ...retainedSource(ROOT_PATH, '\n'), + ...savedSource(ROOT_PATH, '\n'), includes: [root], declarations, }); @@ -1052,7 +1052,7 @@ describe("Tier DM — the private closure is lexical", () => { expect(info.kind).toBe("unresolved"); const validation = yield* validateDocument({ - ...retainedSource(ROOT_PATH, '\n'), + ...savedSource(ROOT_PATH, '\n'), includes: [], }); expect(validation.outcome).toBe("invalid"); @@ -1218,7 +1218,7 @@ describe("Tier DM — selection is journaled and replays", () => { scoped(function* () { return yield* collect( yield* execute({ - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), stream: new InMemoryStream(), includes: [], }), @@ -1269,7 +1269,7 @@ describe("Tier DM — describing the environment agrees with running in it", () it("DM31: validation records the declared origin and checks its contract", function* () { const validation = yield* validateDocument({ - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), includes: [], declarations, }); @@ -1298,7 +1298,7 @@ describe("Tier DM — describing the environment agrees with running in it", () ].join("\n"); const validation = yield* validateDocument({ - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), includes: [], declarations: [declared(source)], }); @@ -1309,7 +1309,7 @@ describe("Tier DM — describing the environment agrees with running in it", () it("DM33: a private name written outside its declaration is an unresolved component", function* () { const validation = yield* validateDocument({ - ...retainedSource(ROOT_PATH, '\n'), + ...savedSource(ROOT_PATH, '\n'), includes: [], declarations, }); @@ -1337,7 +1337,7 @@ describe("Tier DM — structural validation is the same walk without the run's v ].join("\n"); it("DM34: a root declaring required props is structurally valid with no values", function* () { - const source = { ...retainedSource(ROOT_PATH, REQUIRING_PROPS), includes: [] }; + const source = { ...savedSource(ROOT_PATH, REQUIRING_PROPS), includes: [] }; expect((yield* validateDocumentStructure(source)).outcome).toBe("valid"); expect((yield* validateDocument(source)).outcome).toBe("invalid"); @@ -1345,7 +1345,7 @@ describe("Tier DM — structural validation is the same walk without the run's v it("DM35: structural validation still reports everything else the walk decides", function* () { const source = { - ...retainedSource(ROOT_PATH, `${REQUIRING_PROPS}\n`), + ...savedSource(ROOT_PATH, `${REQUIRING_PROPS}\n`), includes: [], }; @@ -1358,7 +1358,7 @@ describe("Tier DM — structural validation is the same walk without the run's v }); it("DM36: structural validation answers the same version and shape", function* () { - const source = { ...retainedSource(ROOT_PATH, "Nothing to resolve.\n"), includes: [] }; + const source = { ...savedSource(ROOT_PATH, "Nothing to resolve.\n"), includes: [] }; expect(yield* validateDocumentStructure(source)).toEqual(yield* validateDocument(source)); }); @@ -1406,7 +1406,7 @@ function* published( yield* collect( yield* executeInstalled( { - ...retainedSource(ROOT_PATH, source), + ...savedSource(ROOT_PATH, source), stream, includes: [], }, @@ -1437,7 +1437,7 @@ function answeringOpenName(definition: ImportedDefinition): ExecutionInstallatio } /** - * The `{ text, exact }` runs one child's close retained, read rather than + * The `{ text, exact }` runs one child's close saved, read rather than * asserted. * * A row about what a durable value *is* cannot cast it into the shape it @@ -1445,13 +1445,13 @@ function answeringOpenName(definition: ImportedDefinition): ExecutionInstallatio * what it found — instead of arriving at the expectation as a plausible shape * nobody checked. */ -function retainedRuns(event: DurableEvent): { text: string; exact: boolean }[] { +function savedRuns(event: DurableEvent): { text: string; exact: boolean }[] { if (event.type !== "close" || event.result.status !== "ok") { return []; } const value = event.result.value; if (!Array.isArray(value)) { - throw new Error(`the close of ${event.coroutineId} retained no runs: ${JSON.stringify(value)}`); + throw new Error(`the close of ${event.coroutineId} saved no runs: ${JSON.stringify(value)}`); } return value.map((entry) => { if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) { @@ -1460,7 +1460,7 @@ function retainedRuns(event: DurableEvent): { text: string; exact: boolean }[] { return { text, exact }; } } - throw new Error(`the close of ${event.coroutineId} retained ${JSON.stringify(entry)}`); + throw new Error(`the close of ${event.coroutineId} saved ${JSON.stringify(entry)}`); }); } @@ -1644,7 +1644,7 @@ describe("Tier DM — exact source is a provenance, not a field", () => { it("DM55: a replayed child restores what its bytes were", function* () { // The disposition is a fact about what canonical expansion produced, and // the segments it marked do not survive the process. A replay therefore - // reads it from the child's own durable close — and a close that retained + // reads it from the child's own durable close — and a close that saved // only text could not answer at all. const source = [ "", @@ -1672,10 +1672,10 @@ describe("Tier DM — exact source is a provenance, not a field", () => { (event) => event.type === "close" && event.coroutineId.includes("."), ); expect(children).toHaveLength(2); - // What each child retained says what its bytes were, not only what they + // What each child saved says what its bytes were, not only what they // said. Read as a set: children close in whatever order they finished, and // which one finished first is exactly what this must not depend on. - const runs = children.flatMap((event) => retainedRuns(event)); + const runs = children.flatMap((event) => savedRuns(event)); expect(runs.filter((run) => run.exact)).toEqual([{ text: PRESENTABLE, exact: true }]); expect(runs.some((run) => !run.exact && run.text.includes("ordinary prose"))).toBe(true); @@ -1704,7 +1704,7 @@ describe("Tier DM — exact source is a provenance, not a field", () => { (event) => !(event.type === "close" && event.coroutineId === "root"), ); - // Every way a retained close can stop being a list of runs: a value that is + // Every way a saved close can stop being a list of runs: a value that is // not a list, a member that is not a run, a member missing one of the two // things a run is, and a member carrying something else besides. const damaged: Json[] = [ @@ -1880,7 +1880,7 @@ describe("Tier MDK — the declaration states its kind", () => { // Validation resolves it rather than reporting it unresolved. const validation = yield* validateDocumentStructure({ - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), includes: [], declarations, }); @@ -1972,7 +1972,7 @@ describe("Tier MDK — the declaration states its kind", () => { return yield* collect( yield* executeInstalled( { - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), stream: new InMemoryStream(), includes: [], }, @@ -2098,7 +2098,7 @@ function* refusedBeforeRoot( scoped(function* () { return yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, source), stream, includes: [] }, + { ...savedSource(ROOT_PATH, source), stream, includes: [] }, installations, ), ); @@ -2361,7 +2361,7 @@ describe("Tier ED — the catalog answers for the set", () => { scoped(function* () { return yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, "The root ran.\n"), stream, includes: [] }, + { ...savedSource(ROOT_PATH, "The root ran.\n"), stream, includes: [] }, [ { declarations: [hostile] }, { @@ -2412,7 +2412,7 @@ describe("Tier ED — the catalog answers for the set", () => { scoped(function* () { return yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, "x\n"), stream: new InMemoryStream(), includes: [] }, + { ...savedSource(ROOT_PATH, "x\n"), stream: new InMemoryStream(), includes: [] }, [declaring([construct, panel()])], ), ); @@ -2437,7 +2437,7 @@ describe("Tier ED — the catalog answers for the set", () => { scoped(function* () { return yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, "x\n"), stream: new InMemoryStream(), includes: [] }, + { ...savedSource(ROOT_PATH, "x\n"), stream: new InMemoryStream(), includes: [] }, installations, ), ); diff --git a/packages/core/tests/document-target-execution.test.ts b/packages/core/tests/document-target-execution.test.ts index d30f000a4..dbc9642cd 100644 --- a/packages/core/tests/document-target-execution.test.ts +++ b/packages/core/tests/document-target-execution.test.ts @@ -2,7 +2,7 @@ * Tier TX — targeted document execution and replay (spec §5.4, §6.11). * * A projected document is not a rendering exercise: what it must prove is that - * a skipped sibling *did not run*, that a retained element kept the identity it + * a skipped sibling *did not run*, that a saved element kept the identity it * has in a full run, and that a journal recorded against one section cannot be * resumed as another. * @@ -200,7 +200,7 @@ describe("Tier TX — targeted execution", () => { expect(seen.names).toContain("inner"); }); - it("TX4: a retained element keeps the expansion ID it has in a full run", function* () { + it("TX4: a saved element keeps the expansion ID it has in a full run", function* () { const whole: Probes = { names: [], ids: [] }; const targeted: Probes = { names: [], ids: [] }; yield* run(inlineSource(SECTIONS), new InMemoryStream(), whole); @@ -218,7 +218,7 @@ describe("Tier TX — targeted execution", () => { * full run — and seeding identity with the target string would break all * three at once. */ - it("TX5: two different targets agree on a shared retained element", function* () { + it("TX5: two different targets agree on a shared saved element", function* () { const alpha: Probes = { names: [], ids: [] }; const beta: Probes = { names: [], ids: [] }; yield* run(inlineSource(SECTIONS, { target: "Alpha" }), new InMemoryStream(), alpha); @@ -353,7 +353,7 @@ describe("Tier TX — targeted execution", () => { expect(text).not.toContain(" requires"); }); - it("TX11: an invalid structure in the retained range fails before any effect", function* () { + it("TX11: an invalid structure in the saved range fails before any effect", function* () { const body = [ "# Title", "", @@ -1048,7 +1048,7 @@ describe("Tier TX — one read across check and replay", () => { * * Without a Close there is no completed-run shortcut, so the run replays the * import and then continues live — which is the shape that lets the guard and - * the replay consumer both reach the same retained event. + * the replay consumer both reach the same saved event. */ function* partialJournal(target: string): Operation { const stream = new InMemoryStream(); @@ -1188,7 +1188,7 @@ describe("Tier TX — preamble boundary", () => { }); /** - * Tier TX — a retained terminal result needs the import that produced it. + * Tier TX — a saved terminal result needs the import that produced it. * * `durableRun` reuses a recorded Close before replaying anything, and the check * phase only ever sees the Yields a journal actually contains. A journal whose @@ -1225,7 +1225,7 @@ describe("Tier TX — a terminal journal without its root import", () => { expect((error as Error).message).toBe(UNREADABLE_RECORD); expect((error as Error).cause).toBe(undefined); expect(isDocumentTargetError(error)).toBe(false); - // Never the retained Close's own outcome. + // Never the saved Close's own outcome. expect((error as Error).message).not.toContain("alpha content"); expect(seen.names).toEqual([]); expect(stream.snapshot().length).toBe(before); @@ -1495,7 +1495,7 @@ describe("Tier TX — a terminal result needs its own root import", () => { }); /** - * Tier TX — a detached retained value is still ordinary JSON. + * Tier TX — a detached saved value is still ordinary JSON. * * Detaching protects the journal; it does not restrict the document. A * replayed value a document goes on to update must behave as it did when the @@ -1528,7 +1528,7 @@ describe("Tier TX — detached values stay mutable", () => { * A genuine partial replay: the binding is restored from the journal and then * written to by an iteration that runs live. * - * The first eval block is retained, so `state` comes back detached from the + * The first eval block is saved, so `state` comes back detached from the * journal. The second is dropped along with the terminal result, so the * update runs for real. A detached member that is not writable fails here. */ @@ -1716,7 +1716,7 @@ describe("Tier TX — a shifting terminal coroutine", () => { throw new Error("the run completed instead of failing"); }); - // Never Alpha's retained output, and never a partial answer either. + // Never Alpha's saved output, and never a partial answer either. expect((error as Error).message).toBe(UNREADABLE_RECORD); expect((error as Error).cause).toBe(undefined); expect((error as Error).message).not.toContain("alpha content"); @@ -1879,16 +1879,16 @@ describe("Tier TX — a terminal result cannot be replaced after admission", () * The execution-owned gate validates a history and replay consumes it. Between * those, public `ReplayGuard` policy runs — `check`, then `admit`, then `decide` * during replay — and it is code any enclosing scope may install. Handing it the - * retained events themselves would let it rewrite the root selection, the + * saved events themselves would let it rewrite the root selection, the * recorded content, an effect description, or a result *after* admission had * accepted them, which is exactly the enforcement the private gate exists to keep * out of public hands. * * Recorded before the fix, resuming a partial Alpha journal as Alpha with - * `check` rewriting the retained root-import target to Beta: the run executed + * `check` rewriting the saved root-import target to Beta: the run executed * the Beta projection. */ -describe("Tier TX — guards observe a copy, not the retained history", () => { +describe("Tier TX — guards observe a copy, not the saved history", () => { /** A partial Alpha journal: the recorded import, no terminal result. */ function* partialAlpha(): Operation { const complete = new InMemoryStream(); @@ -1953,7 +1953,7 @@ describe("Tier TX — guards observe a copy, not the retained history", () => { } } - it("TX72: check cannot rewrite the retained root target", function* () { + it("TX72: check cannot rewrite the saved root target", function* () { const outcome = yield* resume(yield* partialAlpha(), function* () { yield* ReplayGuard.around({ *check([event], next) { @@ -1967,7 +1967,7 @@ describe("Tier TX — guards observe a copy, not the retained history", () => { expectAlphaOnly(outcome); }); - it("TX73: admit cannot rewrite the retained root target", function* () { + it("TX73: admit cannot rewrite the saved root target", function* () { const outcome = yield* resume(yield* partialAlpha(), function* () { yield* ReplayGuard.around({ *admit([history], next) { @@ -1983,7 +1983,7 @@ describe("Tier TX — guards observe a copy, not the retained history", () => { expectAlphaOnly(outcome); }); - it("TX74: decide cannot rewrite the retained root target", function* () { + it("TX74: decide cannot rewrite the saved root target", function* () { const outcome = yield* resume(yield* partialAlpha(), function* () { yield* ReplayGuard.around({ decide([event], next) { diff --git a/packages/core/tests/document-targets.test.ts b/packages/core/tests/document-targets.test.ts index fdfdc7e18..157328f8b 100644 --- a/packages/core/tests/document-targets.test.ts +++ b/packages/core/tests/document-targets.test.ts @@ -32,7 +32,7 @@ import { normalizeLabel, parseDocumentTargetFailure, outlineDocument, - retainedRanges, + savedRanges, selectTarget, } from "../src/document-targets.ts"; import { scanComponentSpans } from "../src/scanner.ts"; @@ -51,7 +51,7 @@ function catalog(body: string): readonly string[] { function project(body: string, selector: string): string { const found = outline(body); const entry = selectTarget(found, selector); - return retainedRanges(found, entry) + return savedRanges(found, entry) .map((range) => body.slice(range.start, range.end)) .join(""); } @@ -551,7 +551,7 @@ describe("Tier DT — projection", () => { expect(projected).not.toContain("other body"); }); - it("DT37: retained headings and the sole title stay in the projection", function* () { + it("DT37: saved headings and the sole title stay in the projection", function* () { const projected = project(SECTIONS, "Test/Node"); expect(projected).toContain("# Title"); expect(projected).toContain("intro"); @@ -559,7 +559,7 @@ describe("Tier DT — projection", () => { expect(projected).toContain("test intro"); }); - it("DT38: with several outermost headings none is retained by default", function* () { + it("DT38: with several outermost headings none is saved by default", function* () { const body = ["pre", "", "# A", "", "a body", "", "# B", "", "b body", ""].join("\n"); expect(project(body, "B")).toBe(["pre", "", "# B", "", "b body", ""].join("\n")); }); @@ -591,7 +591,7 @@ describe("Tier DT — projection", () => { * A document may open at a deeper level than the one supplying its title. The * shallowest heading is then not the first, and anchoring the preamble to it * puts an earlier addressable section inside the preamble — where it is - * retained, rendered, and executed by every other target, while still being a + * saved, rendered, and executed by every other target, while still being a * target in its own right. */ describe("Tier DT — preamble boundary", () => { @@ -622,11 +622,11 @@ describe("Tier DT — preamble boundary", () => { it("DT65: the earlier section remains independently addressable", function* () { // Its subtree runs to the next heading, so the blank line separating them - // is retained exactly as authored. + // is saved exactly as authored. expect(project(OPENS_DEEP, "Before")).toBe(["## Before", "", "before body", "", ""].join("\n")); }); - it("DT66: real preamble text before the first heading is still retained", function* () { + it("DT66: real preamble text before the first heading is still saved", function* () { const withPreamble = `preamble text\n\n${OPENS_DEEP}`; expect(project(withPreamble, "After")).toBe( ["preamble text", "", "# Title", "", "## After", "", "after body", ""].join("\n"), @@ -639,7 +639,7 @@ describe("Tier DT — projected parsing", () => { return yield* parseRootMarkdownDefinition("__root__", "doc.md", body, selector); } - it("DT40: a retained element keeps the offset and line it was authored at", function* () { + it("DT40: a saved element keeps the offset and line it was authored at", function* () { const body = [ "# Title", "", diff --git a/packages/core/tests/document-validation.test.ts b/packages/core/tests/document-validation.test.ts index 6a2c6c365..369e8ccb8 100644 --- a/packages/core/tests/document-validation.test.ts +++ b/packages/core/tests/document-validation.test.ts @@ -34,7 +34,7 @@ import { documentValidationCodeRank, inlineSource, registerComponents, - retainedSource, + savedSource, validateDocument, } from "../mod.ts"; import type { @@ -450,7 +450,7 @@ describe("Tier DV: recursive Markdown sources", () => { ].join("\n"); const { result, seen } = yield* validating( - retainedSource("components/Foo.md", shared, { target: "Chosen" }), + savedSource("components/Foo.md", shared, { target: "Chosen" }), { tree: { "components/Foo.md": shared }, includes: ["components"] }, ); @@ -473,7 +473,7 @@ describe("Tier DV: recursive Markdown sources", () => { // scanning it again. expect(names(result)).toEqual(["Foo", "Foo", "If", "Output"]); - // One read, two views: the retained bytes answered both parses, and nothing + // One read, two views: the saved bytes answered both parses, and nothing // the document wrote was executed to find any of this out. expect(seen.reads).toEqual([]); expect(seen.effects).toEqual([]); @@ -485,18 +485,18 @@ describe("Tier DV: recursive Markdown sources", () => { // The root is untargeted, so its body is the whole file and it *is* this // path's full-definition view. A second scan of that view would show up as // a second `Foo` record. - const retained = yield* validating(retainedSource("components/Foo.md", body), { + const saved = yield* validating(savedSource("components/Foo.md", body), { tree: { "components/Foo.md": body }, includes: ["components"], }); - expect(names(retained.result)).toEqual(["Foo"]); - expect(retained.result.outcome).toBe("valid"); - expect(named(retained.result, "Foo").origin).toEqual({ + expect(names(saved.result)).toEqual(["Foo"]); + expect(saved.result.outcome).toBe("valid"); + expect(named(saved.result, "Foo").origin).toEqual({ kind: "repository", path: "components/Foo.md", }); - expect(retained.seen.reads).toEqual([]); + expect(saved.seen.reads).toEqual([]); // Read from a path, the same identity is read exactly once: the invocation // finds the root already in the source cache. diff --git a/packages/core/tests/elicit-component.test.ts b/packages/core/tests/elicit-component.test.ts index 23df48b2b..8aaeeb012 100644 --- a/packages/core/tests/elicit-component.test.ts +++ b/packages/core/tests/elicit-component.test.ts @@ -572,7 +572,7 @@ describe("Elicit: durability", () => { * A host reading the history back — the REPL's read-only drawer is the one * this exists for (#848) — has to know which fields the person was shown, and * the provider that could have said so is not running any more. So the schema - * is retained where a position is retained: in the description, under its own + * is saved where a position is saved: in the description, under its own * namespaced field, where it cannot change what a replay matches. */ it("retains the compiled schema in the description and the answer as the result", function* () { @@ -776,7 +776,7 @@ function* withRecordedSource( ); } -/** Whether a recorded import retained the source it read. */ +/** Whether a recorded import saved the source it read. */ function isRecordedContent( value: Json | undefined, ): value is { [key: string]: Json; content: string } { diff --git a/packages/core/tests/ephemeral-service.test.ts b/packages/core/tests/ephemeral-service.test.ts index 03de69a46..2bb518354 100644 --- a/packages/core/tests/ephemeral-service.test.ts +++ b/packages/core/tests/ephemeral-service.test.ts @@ -427,7 +427,7 @@ echo after-collision expect(lifecycle).toEqual({ starts: 2, stops: 2 }); }); - it("rejects incompatible retained component history through divergence", function* () { + it("rejects incompatible saved component history through divergence", function* () { const lifecycle = { starts: 0, stops: 0 }; const executions: string[] = []; const history = new InMemoryStream(); @@ -447,16 +447,16 @@ echo after-provider // reader, and `executions` is the record that it ran (#441). expect(executions).toEqual(["echo after-provider"]); - const retained = withoutClose(history); - const before = retained.snapshot(); - const beforeBytes = serialized(retained); + const saved = withoutClose(history); + const before = saved.snapshot(); + const beforeBytes = serialized(saved); const replayExecutions: string[] = []; let failure: unknown; yield* scoped(function* () { yield* useTrackedExec(replayExecutions); yield* registerComponents([changingProvider(true)]); try { - yield* collect(yield* execute({ path: "doc.md", stream: retained })); + yield* collect(yield* execute({ path: "doc.md", stream: saved })); } catch (error) { failure = error; } @@ -465,12 +465,12 @@ echo after-provider expect(failure).toBeInstanceOf(DivergenceError); expect(String(failure)).not.toContain("collides with a live binding"); expect(replayExecutions).toEqual([]); - expect(retained.snapshot()).toEqual(before); - expect(serialized(retained)).toBe(beforeBytes); + expect(saved.snapshot()).toEqual(before); + expect(serialized(saved)).toBe(beforeBytes); expect(lifecycle).toEqual({ starts: 1, stops: 1 }); }); - it("rejects incompatible retained history when collision validation terminates the root", function* () { + it("rejects incompatible saved history when collision validation terminates the root", function* () { const lifecycle = { starts: 0, stops: 0 }; const history = new InMemoryStream(); yield* useServiceStub(Object.freeze({ hostname: SERVICE_HOSTNAME, port: 40_103 }), lifecycle); @@ -480,14 +480,14 @@ echo after-provider yield* registerComponents([changingProvider(false)]); yield* collect(yield* execute({ path: "doc.md", stream: history })); - const retained = withoutClose(history); - const before = retained.snapshot(); - const beforeBytes = serialized(retained); + const saved = withoutClose(history); + const before = saved.snapshot(); + const beforeBytes = serialized(saved); let failure: unknown; yield* scoped(function* () { yield* registerComponents([changingProvider(true)]); try { - yield* collect(yield* execute({ path: "doc.md", stream: retained })); + yield* collect(yield* execute({ path: "doc.md", stream: saved })); } catch (error) { failure = error; } @@ -499,18 +499,18 @@ echo after-provider } expect(failure.cause).toBeInstanceOf(LiveBindingCollisionError); expect(String(failure)).not.toContain("collides with a live binding"); - expect(retained.snapshot()).toEqual(before); - expect(serialized(retained)).toBe(beforeBytes); + expect(saved.snapshot()).toEqual(before); + expect(serialized(saved)).toBe(beforeBytes); expect(lifecycle).toEqual({ starts: 1, stops: 1 }); - yield* collect(yield* execute({ path: "doc.md", stream: retained })); - expect(retained.snapshot().slice(0, before.length)).toEqual(before); + yield* collect(yield* execute({ path: "doc.md", stream: saved })); + expect(saved.snapshot().slice(0, before.length)).toEqual(before); expect( - retained + saved .snapshot() .filter((event) => event.type === "yield" && event.description.type === "eval"), ).toHaveLength(1); - expect(retained.snapshot().at(-1)?.type).toBe("close"); + expect(saved.snapshot().at(-1)?.type).toBe("close"); }); it("rejects ephemeral exports that collide with durable names before execution", function* () { diff --git a/packages/core/tests/eval-bindings.test.ts b/packages/core/tests/eval-bindings.test.ts index c11817c84..5151c66c7 100644 --- a/packages/core/tests/eval-bindings.test.ts +++ b/packages/core/tests/eval-bindings.test.ts @@ -288,7 +288,7 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { let raised: Error | undefined; try { yield* executeInstalled({ ...inlineSource("Read: {props}\n"), stream }, [watching(ran)], { - initialBindings: { props: "retained" }, + initialBindings: { props: "saved" }, }); } catch (error) { raised = error instanceof Error ? error : new Error(String(error)); @@ -308,7 +308,7 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { const ran: string[] = []; const record: Record = {}; Object.defineProperty(record, name, { - value: "retained", + value: "saved", enumerable: true, writable: true, configurable: true, @@ -345,10 +345,10 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { "Value: {inherited} Other: {kept}", "", ].join("\n"), - { inherited: "retained", kept: "retained" }, + { inherited: "saved", kept: "saved" }, ); - expect(output).toContain("Value: authored Other: retained"); + expect(output).toContain("Value: authored Other: saved"); }); it("EB1: refuses input that is not JSON before any installation or document work", function* () { @@ -364,7 +364,7 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { malformed(undefined), // And the whole record being something other than a record. initialization([]), - initialization("retained"), + initialization("saved"), initialization(null), ]) { const stream = new InMemoryStream(); @@ -388,13 +388,13 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { } }); - it("EB1: a retained `__proto__` binding is installed as an ordinary name", function* () { + it("EB1: a saved `__proto__` binding is installed as an ordinary name", function* () { // Its own enumerable member, which is what `JSON.parse` produces and // therefore what a journal read back from a file can hold. Nothing here sets // a prototype: an object literal would have, which is why this is defined. const record: Record = {}; Object.defineProperty(record, "__proto__", { - value: "retained", + value: "saved", enumerable: true, writable: true, configurable: true, @@ -409,7 +409,7 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { // The document read the name, rather than reading whatever the engine's // prototype setter left behind — which under some runtimes is nothing at all. - expect(output).toContain("Read: retained"); + expect(output).toContain("Read: saved"); }); it("EB1: ordinary execute() inherits nothing, so the same document fails", function* () { @@ -417,7 +417,7 @@ describe("Tier T5 — initial root bindings a trusted host supplies", () => { const source = "```js eval\nconst read = inherited;\n```\n"; // The same bytes succeed through the host entrypoint. - const { output } = yield* inheriting(source, { inherited: "retained" }); + const { output } = yield* inheriting(source, { inherited: "saved" }); expect(output).not.toContain("ERROR"); const result = yield* completion({ ...inlineSource(source), stream }); diff --git a/packages/core/tests/eval-persist.test.ts b/packages/core/tests/eval-persist.test.ts index da61060e0..d58b9be11 100644 --- a/packages/core/tests/eval-persist.test.ts +++ b/packages/core/tests/eval-persist.test.ts @@ -192,7 +192,7 @@ describe("Tier T6 — persist modifier", () => { // L8: a persist block the caller projected into a component anchors in the // content scope, so it stops ahead of a persist resource the component - // retained for itself — even though the component retained that one after + // saved for itself — even though the component saved that one after // projecting, which is the order a single scope would release in reverse. it("L8: a projected persist resource stops before the component's own", function* () { const stream = new InMemoryStream(); diff --git a/packages/core/tests/evaluate-component.test.ts b/packages/core/tests/evaluate-component.test.ts index 2259d4e96..167da8707 100644 --- a/packages/core/tests/evaluate-component.test.ts +++ b/packages/core/tests/evaluate-component.test.ts @@ -30,7 +30,7 @@ import { registerComponents } from "../src/components/registration.ts"; import { useSyntax } from "../host.ts"; import { Elicitation } from "../src/elicitation-api.ts"; import type { ElicitationRequest } from "../src/elicitation-api.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { recordedFiles, useRecordedFiles } from "./support/fragment-files.ts"; import type { RecordedFiles } from "./support/fragment-files.ts"; import { answerProvider, implementation, offering } from "./support/answer-provider.ts"; @@ -56,7 +56,7 @@ function independentWrapper(answer: FunctionComponentDefinition): FunctionCompon function syntaxProfile( options: { transform?: (answer: FunctionComponentDefinition) => FunctionComponentDefinition; - retained?: FunctionComponentDefinition[]; + saved?: FunctionComponentDefinition[]; } = {}, ): ExecutionInstallation { let captured = false; @@ -75,7 +75,7 @@ function syntaxProfile( if (answer.kind !== "function") { throw new Error("expected canonical Syntax"); } - options.retained?.push(answer); + options.saved?.push(answer); return options.transform?.(answer) ?? answer; } return answer; @@ -145,13 +145,13 @@ describe("protected generated component answers", () => { const rendered = String(result); expect(rendered).toContain("**Available in this evaluation:** yes"); expect(rendered).toContain("**Available in this evaluation:** no"); - const retained = (yield* stream.readAll()).filter( + const saved = (yield* stream.readAll()).filter( (event) => event.type === "yield" && event.description.type === "syntax_symbols", ); - expect(retained).toHaveLength(1); - // The one retained reading is the text the fragment rendered, rather than a + expect(saved).toHaveLength(1); + // The one saved reading is the text the fragment rendered, rather than a // second reading taken to fill a result the evaluator no longer builds. - const symbols = retained[0]; + const symbols = saved[0]; const value = symbols?.type === "yield" && symbols.result.status === "ok" ? symbols.result.value @@ -381,11 +381,11 @@ describe("protected generated component answers", () => { ).toBe(false); }); - it("rebuilds routes on continuation and cannot resurrect a retained callable", function* () { + it("rebuilds routes on continuation and cannot resurrect a saved callable", function* () { const source = `'} as="answer" />\n`; const stream = new InMemoryStream(); - const retained: FunctionComponentDefinition[] = []; - const original = yield* run(source, [syntaxProfile({ retained })], stream); + const saved: FunctionComponentDefinition[] = []; + const original = yield* run(source, [syntaxProfile({ saved })], stream); const events = yield* stream.readAll(); const admission = events.findIndex( (event) => event.type === "yield" && event.description.type === "generated_xmd", @@ -394,9 +394,9 @@ describe("protected generated component answers", () => { const partial = events.slice(0, admission + 1); expect(yield* run(source, [syntaxProfile()], new InMemoryStream(partial))).toEqual(original); expect(yield* run(source, [syntaxProfile()], new InMemoryStream(events))).toEqual(original); - const held = retained[0]; + const held = saved[0]; if (held === undefined || typeof held.fn !== "function") { - throw new Error("no delegated answer was retained"); + throw new Error("no delegated answer was saved"); } expect(yield* refusal(held.fn({}, { hasContent: () => false }))).toContain("canonical core"); expect( @@ -424,7 +424,7 @@ describe("protected generated component answers", () => { const ROOT_PATH = "evaluate.md"; -const NOTE = "the retained note\n"; +const NOTE = "the saved note\n"; /** A read-only selection over one recorder. */ function reading(files: RecordedFiles): ExecutionInstallation { @@ -457,7 +457,7 @@ function run( ): Operation { return scoped(function* () { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream }, [...installations]), + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream }, [...installations]), ); }); } @@ -489,7 +489,7 @@ describe("Tier FE — a program the document holds", () => { ); const rendered = String(output); - expect(rendered).toContain("the retained note"); + expect(rendered).toContain("the saved note"); expect(rendered).toContain("the other note"); // Through the captured operations, in the order the fragment wrote them. expect(files.performed).toEqual(["read notes.md", "read other.md"]); @@ -565,13 +565,13 @@ describe("Tier FE — a fragment reaches the captured operations and nothing els expect(files.performed).toEqual([]); }); - it("FE13: an operation retained past its execution refuses rather than acting", function* () { + it("FE13: an operation saved past its execution refuses rather than acting", function* () { const files = recordedFiles({ "notes.md": NOTE }); yield* run(`\\n'} />\n`, [reading(files)]); const during = [...files.performed]; // The execution has ended. Whatever a fragment body, a handler or a - // retained callback still holds is bound to operations this execution + // saved callback still holds is bound to operations this execution // revoked at teardown. expect(during).toEqual(["read notes.md"]); expect(files.performed).toEqual(during); @@ -580,10 +580,10 @@ describe("Tier FE — a fragment reaches the captured operations and nothing els describe("Tier FE — the paired form produces its own program", () => { it("FE2: the exact bytes the producer rendered are the admitted source", function* () { - // Not "contains" and not "after trimming": the retained admission is a + // Not "contains" and not "after trimming": the saved admission is a // decision about one exact fragment, and a continuation is held to those // bytes. A projection that trimmed, re-indented or normalised newlines - // would make the retained text disagree with what the producer wrote. + // would make the saved text disagree with what the producer wrote. const PROGRAM = `\n\n\n\n`; const files = recordedFiles({ "notes.md": NOTE, "other.md": "the other note\n" }); const stream = new InMemoryStream(); @@ -643,7 +643,7 @@ describe("Tier FE — the paired form produces its own program", () => { }); expect(rendered).toEqual(["produced"]); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); }); @@ -678,7 +678,7 @@ describe("Tier FE — the paired form produces its own program", () => { // One chain, two payloads, in the order the two sites ran. expect(crossed).toEqual(["producer-input.md", "notes.md"]); expect(files.performed).toEqual(["read notes.md"]); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); }); it("FE2: a producer that fails stops the evaluation before any fragment work", function* () { @@ -937,7 +937,7 @@ describe("Tier FE — the paired form produces its own program", () => { expect(intercepted).toContain("content"); // The projection is not: the program is what the producer rendered, and the // substituted path was never read. - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); expect(files.performed).not.toContain("read substituted.md"); }); @@ -1048,7 +1048,7 @@ describe("Tier FE — an execution with no selection has no evaluation", () => { [reading(files), reading(files)], ); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); }); @@ -1071,7 +1071,7 @@ describe("Tier FE — an execution with no selection has no evaluation", () => { [reading(files)], ); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); }); }); @@ -1109,7 +1109,7 @@ describe("Tier FE — what the element itself may say", () => { const unbound = yield* run(`\\n'} />\n\nbetween\n`, [ reading(loose), ]); - expect(String(unbound)).toContain("the retained note"); + expect(String(unbound)).toContain("the saved note"); expect(String(unbound)).toContain("between"); expect(loose.performed).toEqual(["read notes.md"]); @@ -1122,7 +1122,7 @@ describe("Tier FE — what the element itself may say", () => { [reading(captured)], ); expect(String(bound)).toContain("between"); - expect(String(bound)).not.toContain("the retained note"); + expect(String(bound)).not.toContain("the saved note"); expect(captured.performed).toEqual(["read notes.md"]); // And an ordinary structural capture around it sees exactly the text the @@ -1133,7 +1133,7 @@ describe("Tier FE — what the element itself may say", () => { `\n`, [reading(surrounded)], ); - expect(String(held)).toContain("the retained note"); + expect(String(held)).toContain("the saved note"); expect(surrounded.performed).toEqual(["read notes.md"]); }); }); @@ -1176,7 +1176,7 @@ describe("Tier FE — one occurrence, one durable decision", () => { const second = recordedFiles({ "notes.md": NOTE }); yield* run(source, [reading(second)], new InMemoryStream(partial)); - // The retained admission was restored rather than made again, and the read + // The saved admission was restored rather than made again, and the read // it authorized ran on this attempt. expect(second.performed).toEqual(["read notes.md"]); }); @@ -1184,7 +1184,7 @@ describe("Tier FE — one occurrence, one durable decision", () => { it("FE10: a continuation offering different text refuses before any effect", function* () { // The document is byte-identical across both attempts, so the occurrence // identity — and therefore the durable name — is the same. What differs is - // the program the producer rendered, which is exactly the case a retained + // the program the producer rendered, which is exactly the case a saved // admission has to refuse: a decision was made about one fragment, and the // run is now holding another. const DOCUMENT = `\n\n\n`; @@ -1227,7 +1227,7 @@ describe("Tier FE — one occurrence, one durable decision", () => { ); expect(failed).toContain("the exact text it was made about"); - // Nothing was performed: neither the retained fragment nor the offered one. + // Nothing was performed: neither the saved fragment nor the offered one. expect(second.performed).toEqual([]); }); @@ -1276,12 +1276,12 @@ describe("Tier FE — one occurrence, one durable decision", () => { // it holds now says something else, so a re-read would be visible in the // output rather than merely in the count. expect(second.performed).toEqual([]); - expect(String(replayed)).toContain("the retained note"); + expect(String(replayed)).toContain("the saved note"); expect(String(replayed)).not.toContain("must not read"); expect(String(replayed)).toBe(String(original)); }); - it("FE12: a retained record this version cannot read fails closed", function* () { + it("FE12: a saved record this version cannot read fails closed", function* () { const files = recordedFiles({ "notes.md": NOTE }); const stream = new InMemoryStream(); const source = `\\n'} />\n`; @@ -1462,7 +1462,7 @@ describe("Tier FE35 — structural constructs in a generated fragment", () => { ); const rendered = String(output); - expect(rendered).toContain("the retained note"); + expect(rendered).toContain("the saved note"); expect(rendered).not.toContain("not taken"); // One admitted read, reached through the constructs rather than around them. expect(files.performed).toEqual(["read notes.md"]); @@ -1534,7 +1534,7 @@ describe("Tier FE35 — structural constructs in a generated fragment", () => { // Each of these has its own handler rather than falling through to the // generic structural refusal: the loop ran, the break ended it after one // pass, and the region rendered the read it wrapped. - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); }); @@ -1635,7 +1635,7 @@ describe("Tier FE35 — structural constructs in a generated fragment", () => { // Two iterations of one element: the second import finds the same plan // rather than an exhausted queue. expect(files.performed).toEqual(["read notes.md", "read notes.md"]); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); }); it("FE35: a zero-iteration occurrence consumes no plan", function* () { @@ -1714,10 +1714,10 @@ describe("Tier FE34 — the shared read profile", () => { const rendered = String(output); expect(rendered).toContain("notes.md"); - expect(rendered).toContain("the retained note"); + expect(rendered).toContain("the saved note"); // The vocabulary was bound rather than rendered, so nothing about it // reaches the caller: the fragment renders exactly what it chose to. That - // the occurrence ran at all is the retained reading, not the output. + // the occurrence ran at all is the saved reading, not the output. expect(rendered).not.toContain("Available in this evaluation"); expect( (yield* stream.readAll()).filter( @@ -2168,7 +2168,7 @@ describe("Tier FE34 — the shared read profile", () => { * execution resolves that name once — before the root import, through the * complete ordinary chain — takes the definition the provider published on the * exact final answer, holds each admitted spelling to what that definition - * offers, and seals what it retained. + * offers, and seals what it saved. * * So the rows here are about the two ways that could be weaker than it looks: * an answer nobody published, and an answer that stopped being the thing that @@ -2199,8 +2199,8 @@ describe("Tier FE14 — the chain answers, and the answer is held to what it off ); } - /** The row one admission retained for the name it admitted. */ - function retainedRow(event: DurableEvent): Json { + /** The row one admission saved for the name it admitted. */ + function savedRow(event: DurableEvent): Json { const result = event.type === "yield" ? event.result : undefined; if (result === undefined || result.status !== "ok" || !isRecord(result.value)) { throw new Error("the admission recorded no result"); @@ -2247,7 +2247,7 @@ describe("Tier FE14 — the chain answers, and the answer is held to what it off // admitted it, and what kind of thing answered for the name: an answer the // chain resolved is a different grant from an operation core supplies the // body for, even under the same URL. - expect(retainedRow(admission)).toEqual({ + expect(savedRow(admission)).toEqual({ name: "Open", form: "self-closing", identity: "https://executable.md/components/open/read", @@ -2377,7 +2377,7 @@ describe("Tier FE14 — the chain answers, and the answer is held to what it off const B = implementation("Open", "B ran"); const stream = new InMemoryStream(); - // A provider settles at the capture: the profile sealed what it retained, + // A provider settles at the capture: the profile sealed what it saved, // and a fragment runs that snapshot. One that keeps answering is answering // a *generated* import, which only canonical execution answers — so the // witness refuses it rather than letting a live provider substitute into an @@ -2540,7 +2540,7 @@ describe("Tier FE14 — the chain answers, and the answer is held to what it off // answers the unchanged A, whose declaration offers the identity the // admission was made over; the outer one delegates to it and then answers // with its own published B, whose declaration offers that spelling under a - // different URL. So capture seals B — and then the *retained* policy + // different URL. So capture seals B — and then the *saved* policy // refuses, because the admission was made over one URL and this run states // another behind the same name. const resumedA = implementation("Open", "A ran"); @@ -2581,7 +2581,7 @@ describe("Tier FE14 — the chain answers, and the answer is held to what it off const resumed = implementation("Open", "A ran"); const output = yield* run(OPEN, [backed(resumed)], new InMemoryStream(partial)); - // The retained admission was restored rather than made again, and the body + // The saved admission was restored rather than made again, and the body // it authorized ran on this attempt — once. expect(String(output)).toContain("A ran"); expect(resumed.invoked).toEqual(["A ran"]); @@ -2641,7 +2641,7 @@ describe("Tier FE14 — the chain answers, and the answer is held to what it off ); expect(lookups).toEqual([]); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); }); }); @@ -3022,7 +3022,7 @@ describe("Tier FE36 — canonical `` is a write a person answers", () => expect(requests).toEqual([]); expect(second.performed).toEqual([]); expect(String(replayed)).toBe(String(first)); - // And the retained history is the one it started from. + // And the saved history is the one it started from. expect(yield* replayStream.readAll()).toEqual(complete); }); }); diff --git a/packages/core/tests/evaluate-loaded-copy.test.ts b/packages/core/tests/evaluate-loaded-copy.test.ts index cfd5eb039..bb276e617 100644 --- a/packages/core/tests/evaluate-loaded-copy.test.ts +++ b/packages/core/tests/evaluate-loaded-copy.test.ts @@ -33,12 +33,12 @@ import { executeInstalled } from "../host.ts"; import type { ExecutionInstallation } from "../host.ts"; import { useSelection } from "./support/generated-selection.ts"; import { registerComponents } from "../src/components/registration.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { recordedFiles, useRecordedFiles } from "./support/fragment-files.ts"; import type { RecordedFiles } from "./support/fragment-files.ts"; const ROOT_PATH = "evaluate.md"; -const NOTE = "the retained note\n"; +const NOTE = "the saved note\n"; /** A read-only selection: core's `` and no writing spelling at all. */ function reading(files: RecordedFiles): ExecutionInstallation { @@ -70,7 +70,7 @@ function run( ): Operation { return scoped(function* () { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream }, [...installations]), + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream }, [...installations]), ); }); } @@ -126,7 +126,7 @@ describe("Tier FE — nothing replaces the protected implementation", () => { // and the fragment runs. const files = recordedFiles({ "notes.md": NOTE }); const output = yield* run(READS, [reading(files)]); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); }); @@ -165,7 +165,7 @@ describe("Tier FE — nothing replaces the protected implementation", () => { return yield* run(READS, [reading(clean)]); }); expect(observed).toContain("Evaluate"); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); }); it("FE24: a deliberate middleware refusal stays a refusal", function* () { @@ -310,7 +310,7 @@ describe("Tier FE — ownership is not permission", () => { expect(reached).toEqual(["repository"]); // ...and the fragment did not: it reached the pinned identity the profile // admitted, through the captured operation. - expect(rendered).toContain("the retained note"); + expect(rendered).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); expect(admissions(yield* stream.readAll())).toHaveLength(1); }); @@ -347,7 +347,7 @@ describe("Tier FE — ownership is not permission", () => { const output = yield* scoped(function* () { return yield* run(READS, [reading(files)]); }); - expect(String(output)).toContain("the retained note"); + expect(String(output)).toContain("the saved note"); expect(files.performed).toEqual(["read notes.md"]); }); }); diff --git a/packages/core/tests/evaluate-provider-lifetime.test.ts b/packages/core/tests/evaluate-provider-lifetime.test.ts index 471dcb20a..95acbb0ae 100644 --- a/packages/core/tests/evaluate-provider-lifetime.test.ts +++ b/packages/core/tests/evaluate-provider-lifetime.test.ts @@ -10,7 +10,7 @@ * * The failure this tier exists to prevent is a provider that keeps working * after the run that admitted it. A resolution suspended when the execution is - * cancelled, a request retained past teardown, a losing answer arriving late, + * cancelled, a request saved past teardown, a losing answer arriving late, * and a captured body invoked afterwards are four shapes of the same thing, and * each of them here is asked about state the run actually left behind rather * than about the wording of a refusal. @@ -39,7 +39,7 @@ import { } from "../src/evaluation-profile.ts"; import type { ResolvedAnswer } from "../src/evaluation-profile.ts"; import { useSelection } from "./support/generated-selection.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { answerProvider, implementation, offering } from "./support/answer-provider.ts"; import type { Implementation } from "./support/answer-provider.ts"; import type { ComponentInvocation } from "../src/invocation-identity.ts"; @@ -76,12 +76,12 @@ function run( ): Operation { return scoped(function* () { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream }, [...installations]), + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream }, [...installations]), ); }); } -/** What a retained request refused with when it was used, as a string. */ +/** What a saved request refused with when it was used, as a string. */ function refusalFrom(request: ComponentAnswerRequest | undefined): string { if (request === undefined) { throw new Error("the provider was never asked"); @@ -91,7 +91,7 @@ function refusalFrom(request: ComponentAnswerRequest | undefined): string { } catch (error) { return error instanceof Error ? error.message : String(error); } - throw new Error("expected the retained request to refuse"); + throw new Error("expected the saved request to refuse"); } /** What one execution refused with, as a string. */ @@ -127,7 +127,7 @@ describe("Tier FE15 — a provider's answer belongs to the execution that captur const stream = new InMemoryStream(); const cleanup: string[] = []; const reached = withResolvers(); - const retained: ComponentAnswerRequest[] = []; + const saved: ComponentAnswerRequest[] = []; yield* scoped(function* () { const running = yield* spawn(() => @@ -144,7 +144,7 @@ describe("Tier FE15 — a provider's answer belongs to the execution that captur if (request.name !== "Open") { return yield* next(); } - retained.push(request); + saved.push(request); // Registered before the barrier, so cancellation cannot // arrive between entering the handler and owning the // cleanup. @@ -180,30 +180,30 @@ describe("Tier FE15 — a provider's answer belongs to the execution that captur expect(A.invoked).toEqual([]); // Cancellation is a teardown like any other: the request this handler was // handed belongs to an execution that is over. - expect(refusalFrom(retained[0])).toContain("has ended"); + expect(refusalFrom(saved[0])).toContain("has ended"); }); - it("FE15: a request retained past a successful execution states nothing afterwards", function* () { + it("FE15: a request saved past a successful execution states nothing afterwards", function* () { const A = implementation("Open", "A ran"); - const retained: ComponentAnswerRequest[] = []; + const saved: ComponentAnswerRequest[] = []; // The provider keeps the request its handler received. const output = yield* run(OPEN, [ { install: admits, - componentAnswers: [answerProvider("Open", A.definition, { retain: retained })], + componentAnswers: [answerProvider("Open", A.definition, { retain: saved })], }, ]); expect(String(output)).toContain("A ran"); // The execution has ended. The request is the object the provider kept, // and it refuses rather than recording into a run that is over. - expect(refusalFrom(retained[0])).toContain("has ended"); + expect(refusalFrom(saved[0])).toContain("has ended"); }); it("FE15: a failed execution revokes the request it minted", function* () { const A = implementation("Open", "A ran"); - const retained: ComponentAnswerRequest[] = []; + const saved: ComponentAnswerRequest[] = []; // The third way a run ends. The fragment names something the profile never // admitted, so the run fails after capture succeeded — and teardown is @@ -212,14 +212,14 @@ describe("Tier FE15 — a provider's answer belongs to the execution that captur run(`\\n'} />\n`, [ { install: admits, - componentAnswers: [answerProvider("Open", A.definition, { retain: retained })], + componentAnswers: [answerProvider("Open", A.definition, { retain: saved })], }, ]), ); expect(failed).toContain("did not admit"); expect(A.invoked).toEqual([]); - expect(refusalFrom(retained[0])).toContain("has ended"); + expect(refusalFrom(saved[0])).toContain("has ended"); }); it("FE15: a losing handler's request states nothing into the execution still running", function* () { @@ -548,7 +548,7 @@ describe("Tier FE15 — a provider's answer belongs to the execution that captur expect(yield* sealed({}, invocation)).toBe("A ran"); expect(A.invoked).toEqual(["A ran"]); - // Teardown. Whatever a provider, a handler or a retained callback still + // Teardown. Whatever a provider, a handler or a saved callback still // holds is bound to an execution that has ended. profile.revoke(); diff --git a/packages/core/tests/evaluation-profile.test.ts b/packages/core/tests/evaluation-profile.test.ts index 853a08ba2..69d94443c 100644 --- a/packages/core/tests/evaluation-profile.test.ts +++ b/packages/core/tests/evaluation-profile.test.ts @@ -143,7 +143,7 @@ describe("protected route projection", () => { } }); - it("revokes retained lookup, body, projection and narrowing operations at teardown", function* () { + it("revokes saved lookup, body, projection and narrowing operations at teardown", function* () { const owner = installation(); owner.activate(); const original = owner.protected.get("Admitted")?.fn; diff --git a/packages/core/tests/execution-protocol.test.ts b/packages/core/tests/execution-protocol.test.ts index e0a6d3afa..fe942f452 100644 --- a/packages/core/tests/execution-protocol.test.ts +++ b/packages/core/tests/execution-protocol.test.ts @@ -1090,13 +1090,13 @@ describe("Tier EP — the execution protocol", () => { // A descriptor the caller keeps and edits after the invocation started: // only the callback it held at the start may run. yield* scoped(function* () { - const retained: InvocationObservers = { observed: () => seen.push("observed:A") }; + const saved: InvocationObservers = { observed: () => seen.push("observed:A") }; const replaced = yield* executeObserved( { ...inlineSource(DOC), stream: new InMemoryStream() }, [], - retained, + saved, ); - retained.observed = () => seen.push("observed:B"); + saved.observed = () => seen.push("observed:B"); yield* replaced; }); @@ -1199,14 +1199,14 @@ describe("Tier EP — the execution protocol", () => { expect(order).toEqual(["install"]); }); - it("EP12: every captured admission runs, in order, on the retained history", function* () { + it("EP12: every captured admission runs, in order, on the saved history", function* () { const order: string[] = []; const seen: number[] = []; const admission = (name: string): JournalAdmission => // deno-lint-ignore require-yield - function* (retained) { + function* (saved) { order.push(name); - seen.push(retained.length); + seen.push(saved.length); }; yield* scoped(function* () { @@ -1265,12 +1265,12 @@ describe("Tier EP — the execution protocol", () => { // reflective route that ignores it — and every one has to be ineffective or // throw, with the next admission still seeing the original history. it("EP18: an admission cannot change the history anyone else reads", function* () { - const attempts: Array<{ says: string; edit: (retained: readonly DurableEvent[]) => void }> = [ - { says: "length", edit: (retained) => void Reflect.set(retained, "length", 0) }, - { says: "indexed replacement", edit: (retained) => void Reflect.set(retained, 0, undefined) }, - { says: "deletion", edit: (retained) => void Reflect.deleteProperty(retained, 0) }, - { says: "reverse", edit: (retained) => void [...[]].reverse.call(retained) }, - { says: "splice", edit: (retained) => void [...[]].splice.call(retained, 0, 99) }, + const attempts: Array<{ says: string; edit: (saved: readonly DurableEvent[]) => void }> = [ + { says: "length", edit: (saved) => void Reflect.set(saved, "length", 0) }, + { says: "indexed replacement", edit: (saved) => void Reflect.set(saved, 0, undefined) }, + { says: "deletion", edit: (saved) => void Reflect.deleteProperty(saved, 0) }, + { says: "reverse", edit: (saved) => void [...[]].reverse.call(saved) }, + { says: "splice", edit: (saved) => void [...[]].splice.call(saved, 0, 99) }, ]; // One completed journal, replayed once per attempt. @@ -1293,18 +1293,18 @@ describe("Tier EP — the execution protocol", () => { { admissions: [ // deno-lint-ignore require-yield - function* (retained) { - observed.push(retained.length); + function* (saved) { + observed.push(saved.length); // Ineffective or throwing — either is a refusal to edit. try { - attempt.edit(retained); + attempt.edit(saved); } catch { // A frozen array throws in strict mode; that is the point. } }, // deno-lint-ignore require-yield - function* (retained) { - observed.push(retained.length); + function* (saved) { + observed.push(saved.length); }, ], }, @@ -1503,7 +1503,7 @@ describe("Tier DP — the document protocol", () => { ]; } - /** Every retained event's type and, for a Yield, what it was. */ + /** Every saved event's type and, for a Yield, what it was. */ function shape(stream: InMemoryStream): string[] { return stream .snapshot() @@ -2289,7 +2289,7 @@ describe("Tier DP — the document protocol", () => { expect(order).toEqual(["refused"]); expect(expanded).toEqual([]); // The complete journal: the first preparation's durable effect is - // retained, the root records the refusal, and no later phase happened. + // saved, the root records the refusal, and no later phase happened. expect(shape(journal)).toEqual(["yield:test_preparation(first)", "close:root"]); // Resume that exact history with only the terminal removed. The first @@ -2335,7 +2335,7 @@ describe("Tier DP — the document protocol", () => { expect(resumeExecuted).toEqual([]); expect(resumeOrder).toEqual(["refused"]); expect(resumeExpanded).toEqual([]); - // The retained Yield is still singular, and a fresh terminal records the + // The saved Yield is still singular, and a fresh terminal records the // refusal the resumed run reached. expect(shape(resumeJournal)).toEqual(["yield:test_preparation(first)", "close:root"]); }); @@ -2404,7 +2404,7 @@ describe("Tier DP — the document protocol", () => { expect(replayJournal.snapshot().length).toEqual(recorded.length); // Partial continuation: the root never closed, so the body runs again — - // and the preparation's effect is restored from its retained Yield + // and the preparation's effect is restored from its saved Yield // instead of being performed a second time. const truncated = recorded.filter( (event) => !(event.type === "close" && event.coroutineId === "root"), diff --git a/packages/core/tests/fail-component.test.ts b/packages/core/tests/fail-component.test.ts index fef30901d..2cc202f35 100644 --- a/packages/core/tests/fail-component.test.ts +++ b/packages/core/tests/fail-component.test.ts @@ -418,7 +418,7 @@ describe("Tier FAIL — position, durability and precedence", () => { const second = yield* run(dir, source, { stream }); - // The retained outcome, restored without expanding the root again — the + // The saved outcome, restored without expanding the root again — the // tripwire before the failure would have recorded a second run. expect(failureOf(second).message).toBe("Stop, and stay stopped."); expect(second.ran).toEqual([]); diff --git a/packages/core/tests/file-component.test.ts b/packages/core/tests/file-component.test.ts index 0ffb06802..bd0fe5d0f 100644 --- a/packages/core/tests/file-component.test.ts +++ b/packages/core/tests/file-component.test.ts @@ -1452,7 +1452,7 @@ describe("Tier FL — the authored form decides the effect", () => { // This drives the whole supported public surface: ordinary `execute()`, // `Component.importComponent` middleware, a same-named Context built from the // public API, and core's own ``. The wrapper resolves `` normally - // to retain its dispatcher, then answers `` with that retained + // to retain its dispatcher, then answers `` with that saved // definition while trying to make the frame say it was selected. If the // frames were reachable, the second element would enter ``'s hidden // read body for an import canonical resolution never selected it for. @@ -1468,30 +1468,30 @@ describe("Tier FL — the authored form decides the effect", () => { new InMemoryStream(), function* () { yield* useFileCalls(calls); - let retained: FunctionComponentDefinition | undefined; + let saved: FunctionComponentDefinition | undefined; // Built from the public API, with the name core used while this state // lived in a Context. Whatever it holds must not be the frames. const impostor = createContext("executablemd.core.form-selection", undefined); yield* Component.around({ *importComponent([name, position], next) { - if (name === "Other" && retained !== undefined) { + if (name === "Other" && saved !== undefined) { // Everything a handler can reach, aimed at the frame the engine // is about to settle for this import. const held: unknown = yield* impostor.get(); const frames = Array.isArray(held) ? held : []; for (const frame of frames) { if (typeof frame === "object" && frame !== null) { - Reflect.set(frame, "selected", retained.fn); + Reflect.set(frame, "selected", saved.fn); Reflect.set(frame, "count", 1); } } - yield* impostor.set([{ selected: retained.fn, count: 1 }]); + yield* impostor.set([{ selected: saved.fn, count: 1 }]); answered.push(name); - return retained; + return saved; } const definition = yield* next(name, position); if (name === "File" && definition.kind === "function") { - retained = definition; + saved = definition; } return definition; }, @@ -1500,7 +1500,7 @@ describe("Tier FL — the authored form decides the effect", () => { ); const output = String(rendered); - // The attack ran: the wrapper retained core's own `` definition and + // The attack ran: the wrapper saved core's own `` definition and // answered `` with it. Without this the assertions below would also // hold for an unresolved ``, which is not what is under test. expect(answered).toEqual(["Other"]); diff --git a/packages/core/tests/fixed-filesystem-components.test.ts b/packages/core/tests/fixed-filesystem-components.test.ts index 77768d960..8c03e71b4 100644 --- a/packages/core/tests/fixed-filesystem-components.test.ts +++ b/packages/core/tests/fixed-filesystem-components.test.ts @@ -14,7 +14,7 @@ * around the import. It may observe it, delegate it and refuse it by throwing; * what it cannot do is answer one. A replacement, a rebuilt descriptor, another * name's answer and a separately constructed dispatcher are each refused - * against the copy canonical execution retained, and core's copy is what runs. + * against the copy canonical execution saved, and core's copy is what runs. * * Underneath both, the components are *ordinary*: each body performs its * operations through contextual `API.Files`, which middleware still answers, @@ -604,7 +604,7 @@ describe("Tier FX — the final import answer is verified", () => { const fixture = yield* useFixture(); // A dispatcher built from the same declaration, so every field matches and - // the function is not the one this execution retained. A definition from an + // the function is not the one this execution saved. A definition from an // actual second loaded copy of core is the same class of answer and is // proven against a real bundled copy in `syntax-loaded-copy.test.ts`, which // is Deno-only; this file runs under every supported runtime. diff --git a/packages/core/tests/foreground-exec.test.ts b/packages/core/tests/foreground-exec.test.ts index cb05bcd03..545aebbad 100644 --- a/packages/core/tests/foreground-exec.test.ts +++ b/packages/core/tests/foreground-exec.test.ts @@ -289,7 +289,7 @@ describe("Tier FG — foreground execution", () => { /** * The adapter starts forwarding while it acquires the child, and publishes - * its retained observations through Signals, which drop a send nobody has + * its saved observations through Signals, which drop a send nobody has * subscribed to yet. A run that began retaining after acquisition would keep * a record missing its first bytes — silently, and only sometimes. The child * here writes before this case can possibly have subscribed to anything. @@ -389,7 +389,7 @@ describe("Tier FG — foreground execution", () => { expect(run.seen.stdout).not.toContain("bound"); // stderr stays diagnostic and reaches the reader. expect(run.seen.stderr).toContain("loud"); - // And nothing at all was retained. + // And nothing at all was saved. expect(outcome?.stdout).toBe(undefined); expect(outcome?.stderr).toBe(undefined); // The block after the region is an ordinary foreground block again. @@ -422,8 +422,8 @@ describe("Tier FG — foreground execution", () => { expect(seen.stdout).toBe("from-stdout\n"); expect(seen.stderr).toBe("from-stderr\n"); // And recorded on the operation each was received on. - expect(kept?.retainedStdout).toBe("from-stdout\n"); - expect(kept?.retainedStderr).toBe("from-stderr\n"); + expect(kept?.savedStdout).toBe("from-stdout\n"); + expect(kept?.savedStderr).toBe("from-stderr\n"); }); /** @@ -443,11 +443,11 @@ describe("Tier FG — foreground execution", () => { kept = finished(); }); - expect(kept?.retainedStdout).toBe("€"); - expect(kept?.retainedStderr).toBe("x"); + expect(kept?.savedStdout).toBe("€"); + expect(kept?.savedStderr).toBe("x"); }); - it("FG15: a journaled capture agrees byte for byte with what it retained", function* () { + it("FG15: a journaled capture agrees byte for byte with what it saved", function* () { const euro = new TextEncoder().encode("€"); let kept: ForegroundOutput | undefined; @@ -461,8 +461,8 @@ describe("Tier FG — foreground execution", () => { // The same bytes, decoded once and handed to both. expect(kept?.captured).toBe("€"); - expect(kept?.retainedStdout).toBe("€"); - expect(kept?.retainedStderr).toBe("diagnostic"); + expect(kept?.savedStdout).toBe("€"); + expect(kept?.savedStderr).toBe("diagnostic"); }); /** @@ -572,8 +572,8 @@ describe("Tier FG — foreground execution", () => { expect(seen.stdout).toBe("out"); expect(seen.stderr).toBe("err"); // And the record names the channel each was received on. - expect(kept?.retainedStdout).toBe("out"); - expect(kept?.retainedStderr).toBe("err"); + expect(kept?.savedStdout).toBe("out"); + expect(kept?.savedStderr).toBe("err"); }); /** @@ -582,7 +582,7 @@ describe("Tier FG — foreground execution", () => { * ownership of its own child processes. What it forwards is what the run * has: the boundary keeps the transformed text, not the bytes behind it. */ - it("FG19: transformed bytes an enclosing middleware forwards are what is retained", function* () { + it("FG19: transformed bytes an enclosing middleware forwards are what is saved", function* () { const encoder = new TextEncoder(); const decoder = new TextDecoder(); let transformed = 0; @@ -612,8 +612,8 @@ describe("Tier FG — foreground execution", () => { // What it forwarded is what was displayed and what was kept. expect(seen.stdout).toBe("OUT"); expect(seen.stderr).toBe("err"); - expect(kept?.retainedStdout).toBe("OUT"); - expect(kept?.retainedStderr).toBe("err"); + expect(kept?.savedStdout).toBe("OUT"); + expect(kept?.savedStderr).toBe("err"); }); /** @@ -625,7 +625,7 @@ describe("Tier FG — foreground execution", () => { * are all of this kind — and it decides what a reader sees and nothing else. * Reversing either half is the defect this holds shut. */ - it("FG23: what is retained is set upstream of the boundary and is untouchable below it", function* () { + it("FG23: what is saved is set upstream of the boundary and is untouchable below it", function* () { const encoder = new TextEncoder(); const decoder = new TextDecoder(); let above = 0; @@ -672,11 +672,11 @@ describe("Tier FG — foreground execution", () => { expect(above).toBe(1); expect(below).toBe(2); // The one above decided the record. - expect(upstream.retainedStdout).toBe("rewritten"); - expect(upstream.retainedStderr).toBe("err"); + expect(upstream.savedStdout).toBe("rewritten"); + expect(upstream.savedStderr).toBe("err"); // The one below decided nothing about it. - expect(downstream.retainedStdout).toBe("original"); - expect(downstream.retainedStderr).toBe("err"); + expect(downstream.savedStdout).toBe("original"); + expect(downstream.savedStderr).toBe("err"); }); /** @@ -761,7 +761,7 @@ describe("Tier FG — foreground execution", () => { * status does not travel through the stdio chain and is unaffected, and the * channel the handler left alone arrives as the command emitted it. */ - it("FG25: stdout consumed upstream of the boundary is displayed and retained nowhere", function* () { + it("FG25: stdout consumed upstream of the boundary is displayed and saved nowhere", function* () { const source = ["```bash exec", "echo swallowed; echo kept >&2", "```", ""].join("\n"); let consumed = 0; @@ -779,7 +779,7 @@ describe("Tier FG — foreground execution", () => { const kept = execOutcome(run.stream); expect(consumed).toBeGreaterThan(0); - // Nothing displayed, nothing retained, and no trace in the record. + // Nothing displayed, nothing saved, and no trace in the record. expect(run.seen.stdout).toBe(""); expect(kept?.stdout).toBe(""); expect(JSON.stringify(kept)).not.toContain("swallowed"); @@ -819,12 +819,12 @@ describe("Tier FG — foreground execution", () => { expect(redirects).toBe(1); // Nothing arrived on stdout, so nothing is recorded there. - expect(kept?.retainedStdout).toBe(""); + expect(kept?.savedStdout).toBe(""); expect(seen.stdout).toBe(""); // Both texts arrived on stderr, and both are recorded there. - expect(kept?.retainedStderr).toContain("out"); - expect(kept?.retainedStderr).toContain("err"); - expect(kept?.retainedStderr?.length).toBe("outerr".length); + expect(kept?.savedStderr).toContain("out"); + expect(kept?.savedStderr).toContain("err"); + expect(kept?.savedStderr?.length).toBe("outerr".length); expect(seen.stderr).toContain("out"); expect(seen.stderr).toContain("err"); }); @@ -889,7 +889,7 @@ describe("Tier FG — bound command results", () => { expect(outcome?.stderr).toBe(undefined); }); - it("FG28: a resumed retained run rebuilds the same binding without running again", function* () { + it("FG28: a resumed saved run rebuilds the same binding without running again", function* () { const source = [ '```bash exec as="probe"', "printf recorded-out; printf recorded-err >&2; exit 3", @@ -1363,7 +1363,7 @@ describe("Tier FG — bound command results", () => { /** * `Component.codeBlock` is how an ordinary block's terminal learns what to * run, and it composes publicly. A bound command is executed against the - * context canonical core retained, so a handler answering with another block + * context canonical core saved, so a handler answering with another block * changes neither which command runs nor whether its channels stay off the * reader's terminal. */ @@ -1400,7 +1400,7 @@ describe("Tier FG — bound command results", () => { // The authored command ran and its outcome is what was bound. expect(bound.output).toContain("[5|authored]"); expect(bound.output).not.toContain("hijacked"); - // Routing is the retained one too: neither channel reached the reader. + // Routing is the saved one too: neither channel reached the reader. expect(bound.seen.stdout).toBe(""); expect(bound.seen.stderr).toBe(""); diff --git a/packages/core/tests/function-components.test.ts b/packages/core/tests/function-components.test.ts index b06b62e46..1bbcd266e 100644 --- a/packages/core/tests/function-components.test.ts +++ b/packages/core/tests/function-components.test.ts @@ -761,11 +761,11 @@ describe("Tier O — Eval scope hierarchy", () => { }); }); -describe("Tier RT — Retained resources under durability", () => { +describe("Tier RT — Saved resources under durability", () => { beforeAll(() => useTempFileCompiler()); /** - * One bounded execution. A resource retained at the root belongs to the + * One bounded execution. A resource saved at the root belongs to the * document eval scope, which `execute()` acquires on its caller's frame — * so the scope has to close before the run's effects can be read. */ @@ -788,9 +788,9 @@ describe("Tier RT — Retained resources under durability", () => { // RT15: retention is a property of component execution, so it composes with // durability the way an ordinary resource does (O22). The import and the - // durable effect replay; the retained resource is re-established on each + // durable effect replay; the saved resource is re-established on each // execution that actually runs, and released with its site scope each time. - it("RT15: a retained resource is re-established on every execution", function* () { + it("RT15: a saved resource is re-established on every execution", function* () { const tmpDir = yield* makeTempDir(); yield* writeFiles(tmpDir, { "components/Held.ts": [ @@ -809,8 +809,8 @@ describe("Tier RT — Retained resources under durability", () => { "", "export default function*() {", " const token = yield* retain(function*() {", - " mark('retained', 'acquire');", - " yield* ensure(function*() { mark('retained', 'release'); });", + " mark('saved', 'acquire');", + " yield* ensure(function*() { mark('saved', 'release'); });", " return 'token';", " });", " const stamp = yield* durableCall('held-stamp', () =>", @@ -840,9 +840,9 @@ describe("Tier RT — Retained resources under durability", () => { expect(second).toBe(first); // The durable executor ran once and replayed the second time. expect(yield* lines("executor")).toEqual(["ran"]); - // The retained resource did not: it is re-established per execution and + // The saved resource did not: it is re-established per execution and // released with that execution's site scope — the document scope here. - expect(yield* lines("retained")).toEqual(["acquire", "release", "acquire", "release"]); + expect(yield* lines("saved")).toEqual(["acquire", "release", "acquire", "release"]); }); // RT16: the same request from an eval block is refused. Eval is durable, so a diff --git a/packages/core/tests/generated-composition.test.ts b/packages/core/tests/generated-composition.test.ts index fb2dd2859..5e01b708f 100644 --- a/packages/core/tests/generated-composition.test.ts +++ b/packages/core/tests/generated-composition.test.ts @@ -28,7 +28,7 @@ import { collect } from "../src/collect.ts"; import { executeInstalled } from "../host.ts"; import { useSelection } from "./support/generated-selection.ts"; import type { ExecutionInstallation } from "../host.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { recordedFiles, useRecordedFiles } from "./support/fragment-files.ts"; import type { RecordedFiles } from "./support/fragment-files.ts"; import { answerProvider } from "./support/answer-provider.ts"; @@ -36,7 +36,7 @@ import { registerComponents } from "../src/components/registration.ts"; import type { FunctionComponentDefinition } from "../src/types.ts"; const ROOT_PATH = "workflows/agent.md"; -const NOTE = "the retained note\n"; +const NOTE = "the saved note\n"; const OTHER = "the other note\n"; function reading(files: RecordedFiles): ExecutionInstallation { @@ -84,7 +84,7 @@ function run( ): Operation { return scoped(function* () { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream }, [...installations]), + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream }, [...installations]), ); }); } @@ -129,7 +129,7 @@ describe("Tier FE31 — a fragment binds locally and renders what it chose", () const rendered = String(output); expect(files.performed).toEqual(["read notes.md"]); - expect(rendered).toContain("the retained note"); + expect(rendered).toContain("the saved note"); expect(rendered).not.toContain('"note"'); expect(rendered).not.toContain("observations"); }); diff --git a/packages/core/tests/generated-owner-metadata.test.ts b/packages/core/tests/generated-owner-metadata.test.ts index e71f116d2..2b3c3675f 100644 --- a/packages/core/tests/generated-owner-metadata.test.ts +++ b/packages/core/tests/generated-owner-metadata.test.ts @@ -37,7 +37,7 @@ import { collect } from "../src/collect.ts"; import { executeInstalled } from "../host.ts"; import type { ComponentAnswerInstallation, ExecutionInstallation } from "../host.ts"; import { useSelection } from "./support/generated-selection.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import type { IdentityComponent } from "../src/invocation-identity.ts"; import { Component } from "../src/component-api.ts"; import { registerComponents } from "../src/components/registration.ts"; @@ -263,7 +263,7 @@ describe("Tier GOM — a declaration states what its spellings are offered as", }); // deno-lint-ignore require-yield - it("GOM1: an accessor is read once, and what was validated is what is retained", function* () { + it("GOM1: an accessor is read once, and what was validated is what is saved", function* () { // The check/use gap at the declaration boundary. The identity answers the // accepted URL the first time it is read and `not-a-url` afterwards, so a // registration that validated the declarer's object and then copied it @@ -502,7 +502,7 @@ describe("Tier GOM — a shared definition's metadata cannot be edited", () => { * Every row drives a real execution: the host declares the component, the * execution builds it, a provider publishes that exact built object, and the * fragment names it in the spelling its declaration offers. What the rows read - * is the admission the run retained, because a refusal assertion would pass for + * is the admission the run saved, because a refusal assertion would pass for * a run that admitted the wrong URL and happened to fail later. */ describe("Tier GOM — a declared component's offer survives capture", () => { @@ -587,7 +587,7 @@ describe("Tier GOM — a declared component's offer survives capture", () => { ): Operation { return scoped(function* () { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, NAMING_PROBE), stream }, [ + yield* executeInstalled({ ...savedSource(ROOT_PATH, NAMING_PROBE), stream }, [ ...installations, ]), ); @@ -598,7 +598,7 @@ describe("Tier GOM — a declared component's offer survives capture", () => { return typeof value === "object" && value !== null && !Array.isArray(value); } - /** The row this run's admission retained for the name it admitted. */ + /** The row this run's admission saved for the name it admitted. */ function* admittedRow(stream: InMemoryStream): Operation { const events = yield* stream.readAll(); const event = events.find( @@ -606,7 +606,7 @@ describe("Tier GOM — a declared component's offer survives capture", () => { ); const result = event?.type === "yield" ? event.result : undefined; if (result === undefined || result.status !== "ok" || !isRecord(result.value)) { - throw new Error("the run retained no admission"); + throw new Error("the run saved no admission"); } const policy = result.value.policy; if (!isRecord(policy) || !Array.isArray(policy.allowed)) { diff --git a/packages/core/tests/generated-selection.test.ts b/packages/core/tests/generated-selection.test.ts index e788cf515..1d9d516b3 100644 --- a/packages/core/tests/generated-selection.test.ts +++ b/packages/core/tests/generated-selection.test.ts @@ -37,7 +37,7 @@ import { Execution } from "../api.ts"; import { executeInstalled } from "../host.ts"; import type { ExecutionInstallation } from "../host.ts"; import { registerComponents } from "../src/components/registration.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { recordedFiles, useRecordedFiles } from "./support/fragment-files.ts"; import type { RecordedFiles } from "./support/fragment-files.ts"; import { answerProvider, implementation } from "./support/answer-provider.ts"; @@ -45,7 +45,7 @@ import { useSelection } from "./support/generated-selection.ts"; import type { ComponentDefinition, FunctionComponentDefinition } from "../src/types.ts"; const ROOT_PATH = "selection.md"; -const NOTE = "the retained note\n"; +const NOTE = "the saved note\n"; /** One document, run under one selection. */ interface Attempt { @@ -91,7 +91,7 @@ function run( const output = yield* collect( yield* executeInstalled( { - ...retainedSource(ROOT_PATH, source), + ...savedSource(ROOT_PATH, source), stream, includes: [...(options.includes ?? [])], }, @@ -331,7 +331,7 @@ describe("Tier GS — a selection core cannot capture refuses setup", () => { }); expect(attempt.failure).toBe(undefined); - expect(attempt.output).toContain("the retained note"); + expect(attempt.output).toContain("the saved note"); expect(policy).toEqual(["execute"]); expect(rootImports(attempt.events)).toHaveLength(1); expect(produced).toHaveLength(1); @@ -763,7 +763,7 @@ describe("Tier GS — what a selected name resolves to", () => { } const published = request.answer(definition); // Published, and then edited in place. What the execution - // retained is the copy taken at publication, so this reaches + // saved is the copy taken at publication, so this reaches // nothing — and the answer the chain is now holding no longer // describes what was published, which refuses. Object.assign(definition, { @@ -792,7 +792,7 @@ describe("Tier GS — what a selected name resolves to", () => { const readings: string[] = []; // A declaration whose metadata would answer one thing while it is validated // and another when it is read again. The reading that was validated is the - // reading that is retained, so a value that behaved once is not a value + // reading that is saved, so a value that behaved once is not a value // this execution may retain — and it never gets a second turn to prove it. const definition = { kind: "function" as const, @@ -836,16 +836,16 @@ describe("Tier GS — what a selected name resolves to", () => { expect(attempt.output ?? "").not.toContain("the shifty answer ran"); expect(admissions(attempt.events)).toHaveLength(0); // At most one reading, which is the whole of the protection: what the check - // was made about is what would have been retained, and the second answer + // was made about is what would have been saved, and the second answer // this accessor was waiting to give is never asked for. expect(readings.length).toBeLessThanOrEqual(1); }); - it("U5-3: the retained admission holds the identity the owner declared", function* () { + it("U5-3: the saved admission holds the identity the owner declared", function* () { const ran: string[] = []; // The positive control for the two refusals above: an ordinary declaration - // is retained with the URL its own metadata states, and that is the row the + // is saved with the URL its own metadata states, and that is the row the // admission records. const attempt = yield* run(evaluating("\n"), { installations: [ diff --git a/packages/core/tests/generated-xmd.test.ts b/packages/core/tests/generated-xmd.test.ts index f2a3e0117..c0c4fc714 100644 --- a/packages/core/tests/generated-xmd.test.ts +++ b/packages/core/tests/generated-xmd.test.ts @@ -14,9 +14,9 @@ * middleware, and an answer mutated after delegation each fail before the * component is invoked. * - * **What ran is retained.** The admitted source, the selected root, the pinned + * **What ran is saved.** The admitted source, the selected root, the pinned * identities and the exact request policy cross the journal's secret filter as - * one event, and the observations are retained by their own ordinary effects. + * one event, and the observations are saved by their own ordinary effects. * * Every case that involves a request substitutes a provider at `API.Fetch` and * counts what that provider was asked to perform, separately from the rendered @@ -44,7 +44,7 @@ import { Component, content, hasContent } from "../src/component-api.ts"; import { pinnedJson } from "../src/generated-xmd.ts"; import { fixedFilesystemDefinition } from "../src/components/fixed-filesystem.ts"; import { collect } from "../src/collect.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { useTempFileCompiler } from "../src/temp-file-compiler.ts"; import { SOURCE_POSITION_FIELD } from "../src/source-position.ts"; import { useSecretScannerFactory } from "../src/secrets/policy.ts"; @@ -336,7 +336,7 @@ function evaluate( }); const execution = yield* executeInstalled( { - ...retainedSource(ROOT_PATH, ROOT_SOURCE), + ...savedSource(ROOT_PATH, ROOT_SOURCE), stream, includes: [...(options.includes ?? [])], }, @@ -440,7 +440,7 @@ describe("Tier GX — the trusted-host seam", () => { decision: "admitted", source: "\n", named: [{ name: "Probe", identity: PROBE_IDENTITY, form: "self-closing" }], - // Retained in the result as well as the input, because durable replay + // Saved in the result as well as the input, because durable replay // matches an effect by type and name and never compares a description. policy: { version: 4, @@ -587,7 +587,7 @@ describe("Tier GX — the complete fragment is read first", () => { expect(attempt.failure).toContain("executable code block"); expect(attempt.failure).not.toContain("leaked-fragment-text"); - // The refusal is retained, so what it retains matters as much as what it + // The refusal is saved, so what it retains matters as much as what it // says: the class, and no part of the fragment that caused it. expect(persisted(attempt.events)).not.toContain("leaked-fragment-text"); expect(persisted(attempt.events)).toContain('"construct":"block"'); @@ -851,7 +851,7 @@ describe("Tier GX — what the run keeps", () => { expect(attempt.failure).toBeDefined(); expect(transport.performed.map((call) => call.url)).toEqual([URL_ONE, URL_TWO]); - // The admission and the first effect are both retained; the second is not. + // The admission and the first effect are both saved; the second is not. expect(admissions(attempt.events)).toHaveLength(1); expect(observations(attempt.events)).toHaveLength(1); }); @@ -891,7 +891,7 @@ describe("Tier GX — what the run keeps", () => { yield* collect( yield* executeInstalled( { - ...retainedSource(ROOT_PATH, ROOT_SOURCE), + ...savedSource(ROOT_PATH, ROOT_SOURCE), stream: holdingAfterFirst(blocked, reached), }, [installation], @@ -904,7 +904,7 @@ describe("Tier GX — what the run keeps", () => { const interrupted = yield* blocked.readAll(); expect(admissions(interrupted)).toHaveLength(1); - // Exactly the completed one, retained through the halt. + // Exactly the completed one, saved through the halt. expect(observations(interrupted)).toHaveLength(1); }); @@ -932,7 +932,7 @@ describe("Tier GX — what the run keeps", () => { }); yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, ROOT_SOURCE), stream: holding, includes: [] }, + { ...savedSource(ROOT_PATH, ROOT_SOURCE), stream: holding, includes: [] }, [installation], ), ); @@ -963,9 +963,9 @@ describe("Tier GX — what the run keeps", () => { * The admission, every durable effect the admitted fragment performs, and any * later durable effect of the owning operation are one offered sequence, in * authored order. A partial replay offers the same sequence: completed - * generated effects restore from their retained records without entering + * generated effects restore from their saved records without entering * their providers, and the run continues to the later effect rather than - * diverging at the first retained nested record. + * diverging at the first saved nested record. */ describe("Tier GX — nested generated effects belong to the owning expansion", () => { beforeAll(() => useTempFileCompiler()); @@ -1053,7 +1053,7 @@ describe("Tier GX — nested generated effects belong to the owning expansion", expect(again.failure).toBe(undefined); expect(again.output).toBe(first.output); // One live call each, across both executions: the completed read and the - // completed marker restore from their retained records. + // completed marker restore from their saved records. expect(transport.performed).toHaveLength(1); expect(executed).toEqual(["parent-marker"]); expect(offered(again.events)).toEqual([ @@ -1170,7 +1170,7 @@ describe("Tier GX — nested generated effects belong to the owning expansion", * Preflight settled what a fragment may *name*; whether a request is well * formed at all is the component's own question, asked where the request is * made — after the admission has already been recorded, which is where the - * exact source is retained on purpose. So there is no pre-admission refusal + * exact source is saved on purpose. So there is no pre-admission refusal * left to keep the candidate's text out of, and nothing here to assert about * one. What an admitted read may reach is proved against a live bound in * `shared-operation-permissions` Tier PRM, and ``'s own refusals are @@ -1247,7 +1247,7 @@ describe("Tier GX — a resumed run is held to the selection it was admitted und }); yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, ROOT_SOURCE), stream: holding, includes: [] }, + { ...savedSource(ROOT_PATH, ROOT_SOURCE), stream: holding, includes: [] }, [installation], ), ); @@ -1575,7 +1575,7 @@ describe("Tier GX — a resumed run is held to the selection it was admitted und } /** - * Every retained record this build refuses, and what is wrong with each. + * Every saved record this build refuses, and what is wrong with each. * * Their positive control is the current record above: every row here is a * record that would otherwise resume with one thing wrong, so a row that @@ -1761,7 +1761,7 @@ describe("Tier GX — a resumed run is held to the selection it was admitted und ]; for (const [what, admission] of HOSTILE) { - it(`FE18/GX21y: a retained record with ${what} refuses before any effect`, function* () { + it(`FE18/GX21y: a saved record with ${what} refuses before any effect`, function* () { const first = yield* evaluate(request("\n", [probe()])); const performed: string[] = []; @@ -1870,7 +1870,7 @@ describe("Tier GX — a resumed run is held to the selection it was admitted und expect(String(again.output ?? "")).not.toContain("an extra sentence"); }); - it("GX24b: an unsafe current source refuses rather than replaying the retained one", function* () { + it("GX24b: an unsafe current source refuses rather than replaying the saved one", function* () { const first = yield* evaluate(request("\n", [probe()])); const performed: string[] = []; @@ -1884,11 +1884,11 @@ describe("Tier GX — a resumed run is held to the selection it was admitted und expect(again.failure).toContain("no longer offers"); expect(performed).toEqual([]); // Neither the block nor its text reaches the journal: the refusal happens - // before the fragment is walked, so the candidate is never retained. + // before the fragment is walked, so the candidate is never saved. expect(persisted(again.events)).not.toContain("unsafe-block-executed"); }); - it("GX24c: the unchanged text resumes from the retained copy", function* () { + it("GX24c: the unchanged text resumes from the saved copy", function* () { const first = yield* evaluate(request("\n", [probe()])); // The positive control the two refusals above need: identical text still @@ -1903,7 +1903,7 @@ describe("Tier GX — a resumed run is held to the selection it was admitted und }); }); -describe("Tier GX — the secret gate covers what is retained", () => { +describe("Tier GX — the secret gate covers what is saved", () => { beforeAll(() => useTempFileCompiler()); const ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"; @@ -1940,7 +1940,7 @@ describe("Tier GX — the secret gate covers what is retained", () => { if (admission === undefined || observation === undefined) { throw new Error("the gate was not given both events"); } - // The whole retained admission reaches the gate before it persists: the + // The whole saved admission reaches the gate before it persists: the // candidate's own request, the Workspace basis, and the URL the admitted // spelling runs under beside the effect table that admitted it. for (const marker of [ @@ -2035,7 +2035,7 @@ function* useWorkspaceFiles(root: string): Operation { describe("Tier WGAC — the pinned read-only File", () => { it("WGAC1: a self-closing File reads through the ordinary Files provider", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { const reads = yield* useWorkspaceFiles(root); @@ -2044,7 +2044,7 @@ describe("Tier WGAC — the pinned read-only File", () => { }); expect(attempt.evaluated.failure).toBe(undefined); - expect(attempt.evaluated.output).toContain("the retained note"); + expect(attempt.evaluated.output).toContain("the saved note"); // The ordinary component, and therefore the installed provider — not a // second filesystem path of the evaluator's own. expect(attempt.reads).toEqual(["notes.md"]); @@ -2052,7 +2052,7 @@ describe("Tier WGAC — the pinned read-only File", () => { }); it("WGAC1: the read entry admits the reading spelling and nothing else", function* () { - // A retained admission resumes only under the identity the admitted + // A saved admission resumes only under the identity the admitted // spelling runs under, and ``'s two spellings are offered under two // URLs: the reading form observes, the writing form mutates. So this entry // is the self-closing one, and a run that admitted the paired spelling @@ -2068,7 +2068,7 @@ describe("Tier WGAC — the pinned read-only File", () => { const deletion = pinnedFileDelete(); expect(deletion.name).toBe("File.Delete"); - // What a retained admission is compared against, and what a continuation + // What a saved admission is compared against, and what a continuation // that selected the write table is held to: the URL core's own declaration // offers this spelling under, and the effect it offers it as. expect(deletion.definition.generated).toEqual({ @@ -2087,7 +2087,7 @@ describe("Tier WGAC — the pinned read-only File", () => { it("WGAC8: the result carries each observation's value, in order, beside the rendering", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const transport = yield* useTransport(() => ({ status: 200, body: "answered" })); // One admitted read that renders its value, one that renders nothing at all: @@ -2104,7 +2104,7 @@ describe("Tier WGAC — the pinned read-only File", () => { expect(attempt.failure).toBe(undefined); expect(transport.performed.map((call) => call.url)).toEqual([URL_ONE]); - // The identities live in the retained admission, which is where a run is + // The identities live in the saved admission, which is where a run is // held to them — not on the result, which would be a second copy of the // same fact. const admitted = admittedFragments(attempt.events)[0]; @@ -2119,13 +2119,13 @@ describe("Tier WGAC — the pinned read-only File", () => { // response reached the fragment and the fragment did not ask for it, so // nothing carries it outward. A fragment that wants it binds it and renders // it explicitly, which is what the composition tests below do. - expect(attempt.output).toContain("the retained note"); + expect(attempt.output).toContain("the saved note"); expect(attempt.output).not.toContain("answered"); }); it("WGAC2: a paired File anywhere in a mixed fragment performs no read and no write", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const source = `\n\n` + `the agent wrote this\n`; @@ -2165,7 +2165,7 @@ describe("Tier WGAC — the pinned read-only File", () => { it("WGAC2: an unadmitted component after an admitted read performs zero reads", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const source = `\n\n\n`; @@ -2264,7 +2264,7 @@ describe("Tier GXC — a selection is not a grant", () => { expect(omitted.failure).toBe(undefined); expect(explicit.failure).toBe(undefined); expect(explicit.output).toBe(omitted.output); - // The same retained policy, down to the class it normalized to: a document + // The same saved policy, down to the class it normalized to: a document // that says nothing and one that says `read` are held to one grant. expect(recordedPolicy(admittedFragments(explicit.events)[0])).toEqual( recordedPolicy(admittedFragments(omitted.events)[0]), @@ -2274,7 +2274,7 @@ describe("Tier GXC — a selection is not a grant", () => { }); }); - it("GXC1b: a mixed selection is retained in canonical order", function* () { + it("GXC1b: a mixed selection is saved in canonical order", function* () { const attempt = yield* evaluate( selecting("\n", [probe()], { allow: ["write", "read"], @@ -2559,7 +2559,7 @@ describe("Tier GXC — a name is not a form", () => { it("GXC3: each form resolves to its own pinned identity", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { const files = yield* useWorkspaceFiles(root); @@ -2610,7 +2610,7 @@ describe("Tier GXC — a name is not a form", () => { for (const [what, source, allow, diagnostic] of FORMS) { it(`GXC4: ${what} is refused with no effect`, function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { const files = yield* useWorkspaceFiles(root); @@ -2627,7 +2627,7 @@ describe("Tier GXC — a name is not a form", () => { expect(attempt.files).toEqual([]); expect(refusals(attempt.evaluated.events)).toHaveLength(1); expect(admittedFragments(attempt.evaluated.events)).toHaveLength(0); - expect(yield* readTextFile(join(root, "notes.md"))).toBe("the retained note\n"); + expect(yield* readTextFile(join(root, "notes.md"))).toBe("the saved note\n"); }); } @@ -2640,7 +2640,7 @@ describe("Tier GXC — a name is not a form", () => { yield* ensureDir(join(workspace, "File")); yield* writeTextFile(join(workspace, "File", "Delete.md"), "the repository File.Delete ran.\n"); const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); yield* writeTextFile(join(root, "stale.md"), "the note an earlier step left\n"); const attempt = yield* scoped(function* () { @@ -2681,7 +2681,7 @@ describe("Tier GXC — a name is not a form", () => { for (const [what, element] of EXCLUDED) { it(`GXC6: ${what} is outside the write table`, function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { const files = yield* useWorkspaceFiles(root); @@ -2740,7 +2740,7 @@ describe("Tier GXC — a write is not an observation", () => { it("GXC7: a mixed fragment renders the read's own output and no receipt for the write", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { yield* useWorkspaceFiles(root); @@ -2756,7 +2756,7 @@ describe("Tier GXC — a write is not an observation", () => { expect(attempt.failure).toBe(undefined); // The read is written without `as`, so its own value is its ordinary // output; the write beside it renders nothing and adds no receipt. - expect(attempt.output).toContain("the retained note"); + expect(attempt.output).toContain("the saved note"); expect(attempt.output).not.toContain("proposed"); }); @@ -2963,7 +2963,7 @@ describe("Tier GXC — the authored form survives the public content chain", () for (const [what, script] of REPORTING) { it(`GXC10: an admitted read still reads under ${what}`, function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const answered: boolean[] = []; const attempt = yield* scoped(function* () { @@ -2986,11 +2986,11 @@ describe("Tier GXC — the authored form survives the public content chain", () expect(String(attempt.evaluated.output)).toContain("says:"); // One read, no write, and the file it was admitted to read is unchanged. expect(attempt.files).toEqual(["notes.md"]); - expect(yield* readTextFile(join(root, "notes.md"))).toBe("the retained note\n"); + expect(yield* readTextFile(join(root, "notes.md"))).toBe("the saved note\n"); // Both are written without `as`, so each contributes its ordinary output // in invocation order and nothing is collected beside them. expect(String(attempt.evaluated.output)).toContain(`says:${script[0]}`); - expect(String(attempt.evaluated.output)).toContain("the retained note"); + expect(String(attempt.evaluated.output)).toContain("the saved note"); // And the admission still names the identity and form it was granted for. expect(recordedNames(admittedFragments(attempt.evaluated.events)[0])).toEqual([ { @@ -3060,7 +3060,7 @@ describe("Tier GXC — the authored form survives the public content chain", () it("GXC10: an invocation the evaluator did not receive is refused", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { const files = yield* useWorkspaceFiles(root); @@ -3101,12 +3101,12 @@ describe("Tier GXC — the authored form survives the public content chain", () expect(attempt.evaluated.failure).toBeDefined(); expect(attempt.files).toEqual([]); - expect(yield* readTextFile(join(root, "notes.md"))).toBe("the retained note\n"); + expect(yield* readTextFile(join(root, "notes.md"))).toBe("the saved note\n"); }); it("GXC10: an honest chain still runs both forms through ordinary providers", function* () { const root = yield* useWorkspace(); - yield* writeTextFile(join(root, "notes.md"), "the retained note\n"); + yield* writeTextFile(join(root, "notes.md"), "the saved note\n"); const attempt = yield* scoped(function* () { const files = yield* useWorkspaceFiles(root); @@ -3152,7 +3152,7 @@ describe("Tier GXC — the authored form survives the public content chain", () describe("Tier GS — generated work names its own admission", () => { beforeAll(() => useTempFileCompiler()); - /** The position one recorded effect retained, exactly as the journal holds it. */ + /** The position one recorded effect saved, exactly as the journal holds it. */ function positionOf(events: DurableEvent[], type: string): Json | undefined { const found = events.find((event) => event.type === "yield" && event.description.type === type); if (found === undefined || found.type !== "yield") { diff --git a/packages/core/tests/guarded-journal.test.ts b/packages/core/tests/guarded-journal.test.ts index 5240822fb..82881c9fd 100644 --- a/packages/core/tests/guarded-journal.test.ts +++ b/packages/core/tests/guarded-journal.test.ts @@ -353,7 +353,7 @@ describe("the default scanner over a persistence backend", () => { expect(error.message).not.toContain(CANARY); expect(error.stack ?? "").not.toContain(CANARY); - // Read back through the stream rather than from anything the run retained. + // Read back through the stream rather than from anything the run saved. const persisted = yield* backend.readAll(); expect(persisted.some((event) => event.type === "yield")).toBe(false); expect(persisted.map(label)).toEqual(["close(root)"]); diff --git a/packages/core/tests/invocation-identity.test.ts b/packages/core/tests/invocation-identity.test.ts index 3dc35383f..5951c610b 100644 --- a/packages/core/tests/invocation-identity.test.ts +++ b/packages/core/tests/invocation-identity.test.ts @@ -3,7 +3,7 @@ * (specs/executable-mdx-spec.md §5.6). * * A component that names a durable operation after its own invocation is - * asserting ownership: the name decides which retained record a replay + * asserting ownership: the name decides which saved record a replay * restores, and an implementation running under somebody else's identity * commits against its own storage under their expansion. Code Rule 15 says a * decision like that never trusts replaceable state, so nothing here is read @@ -613,7 +613,7 @@ describe("Tier CIV — the identity a host's component names its work after", () // ``. The harness only installs the adversary — it // retains the declared implementation at the first site's own resolution, // then registers the name again so the nested site resolves to that - // registration, and runs what it retained there with the nested site's own + // registration, and runs what it saved there with the nested site's own // genuine invocation. const source = yield* readTextFile( fileURLToPath( @@ -668,7 +668,7 @@ describe("Tier CIV — the identity a host's component names its work after", () *fn(props: Record, invocation: ComponentInvocation) { const borrowed = kept; if (typeof borrowed !== "function") { - nested.push("nothing retained"); + nested.push("nothing saved"); return ""; } return yield* borrowed(props, invocation); @@ -678,7 +678,7 @@ describe("Tier CIV — the identity a host's component names its work after", () }); }); - // The declared site named itself. The nested site ran the retained + // The declared site named itself. The nested site ran the saved // implementation with its own genuine invocation and named nothing: // canonical resolution selected the nested registration there, so that // invocation is in no domain. Restoring identity by authored-name equality diff --git a/packages/core/tests/invocation-scope.test.ts b/packages/core/tests/invocation-scope.test.ts index 79aa72f40..ff470392f 100644 --- a/packages/core/tests/invocation-scope.test.ts +++ b/packages/core/tests/invocation-scope.test.ts @@ -29,7 +29,7 @@ import { describe("Tier O — Eval scope hierarchy", () => { // O3: the invocation's eval scope dies with the invocation, not the document. - it("O3: a resource retained by a component invocation stops when it completes", function* () { + it("O3: a resource saved by a component invocation stops when it completes", function* () { const timeline: string[] = []; const definitions = { Holder: markdown("Holder", "```sh watch exec\nown\n```\n\nbody") }; diff --git a/packages/core/tests/journal-source-position.test.ts b/packages/core/tests/journal-source-position.test.ts index 87c086558..ffa916cdd 100644 --- a/packages/core/tests/journal-source-position.test.ts +++ b/packages/core/tests/journal-source-position.test.ts @@ -4,7 +4,7 @@ * A journal entry's identity is its `type` and `name`. A position is neither, * so it travels beside them under one stable namespaced field and takes no part * in matching — which is the whole reason it can be added to an existing - * description at all. These tests hold both halves: the position is retained for + * description at all. These tests hold both halves: the position is saved for * the operations an author wrote, and replay still matches the entries it * matched before. * @@ -81,7 +81,7 @@ function useMark(): Operation { ]); } -/** The position one operation retained, or `undefined` when it retained none. */ +/** The position one operation saved, or `undefined` when it saved none. */ function sourceOf(events: DurableEvent[], type: string, name?: string): unknown { const found = events.find( (event) => @@ -164,7 +164,7 @@ describe("Tier JSP — authored source in the journal", () => { const recorded = (yield* stream.readAll()).length; // The same journal, replayed: every entry matches by type and name, and a - // full replay adds nothing to what was already retained. + // full replay adds nothing to what was already saved. const replayed = yield* collect(yield* execute({ path, stream })); expect(replayed).toEqual(first); diff --git a/packages/core/tests/protected-content.test.ts b/packages/core/tests/protected-content.test.ts index 1a66c48c1..4f35708c7 100644 --- a/packages/core/tests/protected-content.test.ts +++ b/packages/core/tests/protected-content.test.ts @@ -114,16 +114,16 @@ describe("Tier PCL — one projection, then nothing", () => { expect(underlying.calls).toHaveLength(0); }); - it("PCL3: a retained callback called after close refuses", function* () { + it("PCL3: a saved callback called after close refuses", function* () { const underlying = counted(); const lease = protectedContentLease("Evaluate", underlying.perform); // Exactly what a body keeping the callback in a closure, on a returned // object, or in something it spawned would hold. - const retained = lease.project; + const saved = lease.project; lease.close(); - const refused = yield* refusalOf(retained(REFERENCE)); + const refused = yield* refusalOf(saved(REFERENCE)); expect(refused).toBeInstanceOf(ComponentInvocationError); expect(underlying.calls).toHaveLength(0); }); diff --git a/packages/core/tests/retain.test.ts b/packages/core/tests/retain.test.ts index 7860fd34e..fd5a2a5a2 100644 --- a/packages/core/tests/retain.test.ts +++ b/packages/core/tests/retain.test.ts @@ -1,5 +1,5 @@ /** - * Tier RT — retained resources (spec §4.4). + * Tier RT — saved resources (spec §4.4). * * `retain()` creates a resource owned by the scope that invoked the component, * so it outlives the invocation and is released with the site — while nothing @@ -40,7 +40,7 @@ function retainer(name: string, timeline: string[], label: string) { ); } -describe("Tier RT — Retained resources", () => { +describe("Tier RT — Saved resources", () => { // RT1: the contract self-closing components are for — capture the value with // `as`, then have a later sibling read the binding while the resource behind // it is still alive. @@ -53,7 +53,7 @@ describe("Tier RT — Retained resources", () => { return yield* retain(() => (function* (): Operation { yield* useWatch(timeline, "dir"); - return "/retained/path"; + return "/saved/path"; })(), ); })(), @@ -78,41 +78,41 @@ describe("Tier RT — Retained resources", () => { // The capture renders nothing at the call site, and the sibling resolves // the binding it wrote. expect(output).not.toContain("\n\n`, definitions, timeline); // `own` belongs to the later sibling invocation. It starts and stops - // entirely inside the window where `retained` is still alive. - expect(timeline).toEqual(["start:retained", "start:own", "stop:own", "stop:retained"]); + // entirely inside the window where `saved` is still alive. + expect(timeline).toEqual(["start:saved", "start:own", "stop:own", "stop:saved"]); }); // RT3: the site scope's ordinary completion releases it. - it("RT3: a retained resource is released when the site scope succeeds", function* () { + it("RT3: a saved resource is released when the site scope succeeds", function* () { const timeline: string[] = []; - const definitions = { Holder: retainer("Holder", timeline, "retained") }; + const definitions = { Holder: retainer("Holder", timeline, "saved") }; yield* expandAll(``, definitions, timeline); - expect(timeline).toEqual(["start:retained", "stop:retained"]); + expect(timeline).toEqual(["start:saved", "stop:saved"]); }); // RT4: a failure at the site tears the resource down on the way out. - it("RT4: a retained resource is released when the site scope errors", function* () { + it("RT4: a saved resource is released when the site scope errors", function* () { const timeline: string[] = []; - const definitions = { Holder: retainer("Holder", timeline, "retained") }; + const definitions = { Holder: retainer("Holder", timeline, "saved") }; let raised: unknown; try { @@ -129,13 +129,13 @@ describe("Tier RT — Retained resources", () => { } expect(raised).toBeInstanceOf(Error); - expect(timeline).toEqual(["start:retained", "stop:retained"]); + expect(timeline).toEqual(["start:saved", "stop:saved"]); }); // RT5: cancellation of the site scope, with the resource live at halt time. - it("RT5: a retained resource is released when the site scope is cancelled", function* () { + it("RT5: a saved resource is released when the site scope is cancelled", function* () { const timeline: string[] = []; - const definitions = { Holder: retainer("Holder", timeline, "retained") }; + const definitions = { Holder: retainer("Holder", timeline, "saved") }; yield* race([ scoped(function* () { @@ -149,12 +149,12 @@ describe("Tier RT — Retained resources", () => { sleep(50), ]); - expect(timeline).toEqual(["start:retained", "stop:retained"]); + expect(timeline).toEqual(["start:saved", "stop:saved"]); }); // RT6: an enclosing invocation is itself a site. Its content scope owns what - // was retained there, so nesting unwinds leaf-first. - it("RT6: nested site scopes release retained resources leaf-first", function* () { + // was saved there, so nesting unwinds leaf-first. + it("RT6: nested site scopes release saved resources leaf-first", function* () { const timeline: string[] = []; const definitions = { Outer: component("Outer", () => @@ -163,17 +163,17 @@ describe("Tier RT — Retained resources", () => { return yield* content(); })(), ), - Inner: retainer("Inner", timeline, "inner-retained"), + Inner: retainer("Inner", timeline, "inner-saved"), }; yield* expandAll(`\n\n`, definitions, timeline); - // `inner-retained` is owned by Outer's content scope — stage 1 of Outer's + // `inner-saved` is owned by Outer's content scope — stage 1 of Outer's // teardown — so it stops before Outer releases its own resource. expect(timeline).toEqual([ "start:outer", - "start:inner-retained", - "stop:inner-retained", + "start:inner-saved", + "stop:inner-saved", "stop:outer", ]); }); @@ -202,7 +202,7 @@ describe("Tier RT — Retained resources", () => { // says so rather than silently falling back to invocation lifetime. it("RT8: retain() without an invocation-site scope reports the missing scope", function* () { const timeline: string[] = []; - const definitions = { Holder: retainer("Holder", timeline, "retained") }; + const definitions = { Holder: retainer("Holder", timeline, "saved") }; const expanded = yield* scoped(function* () { yield* useHarness(definitions, timeline); @@ -220,7 +220,7 @@ describe("Tier RT — Retained resources", () => { // resource in a scope with no relationship to the call site. it("RT8b: a missing site does not fall through to an inherited provider", function* () { const timeline: string[] = []; - const definitions = { Holder: retainer("Holder", timeline, "retained") }; + const definitions = { Holder: retainer("Holder", timeline, "saved") }; const expanded = yield* scoped(function* () { // A stray provider in an enclosing context, of the shape an embedding @@ -245,26 +245,26 @@ describe("Tier RT — Retained resources", () => { expect(timeline).toEqual([]); }); - // RT9: a resource retained at the root lives for the expansion, not for the + // RT9: a resource saved at the root lives for the expansion, not for the // element — the site scope there is the document's. - it("RT9: a resource retained at the root outlives every later sibling", function* () { + it("RT9: a resource saved at the root outlives every later sibling", function* () { const timeline: string[] = []; const definitions = { - Holder: retainer("Holder", timeline, "retained"), + Holder: retainer("Holder", timeline, "saved"), Wrap: markdown("Wrap", `${WATCH_BLOCK_OWN}\n\n`), }; yield* expandAll(`\n\ntext`, definitions, timeline); - expect(timeline).toEqual(["start:retained", "start:own", "stop:own", "stop:retained"]); + expect(timeline).toEqual(["start:saved", "start:own", "stop:own", "stop:saved"]); }); // RT10: halting mid-expansion, while a later block suspends, still releases - // what an earlier element retained. - it("RT10: halting expansion releases what an earlier element retained", function* () { + // what an earlier element saved. + it("RT10: halting expansion releases what an earlier element saved", function* () { const timeline: string[] = []; const definitions = { - Holder: retainer("Holder", timeline, "retained"), + Holder: retainer("Holder", timeline, "saved"), Wrap: markdown("Wrap", ""), }; @@ -272,11 +272,11 @@ describe("Tier RT — Retained resources", () => { expandAll(`\n\n\n${HANG_BLOCK}\n`, definitions, timeline), ); yield* sleep(20); - expect(timeline).toContain("start:retained"); + expect(timeline).toContain("start:saved"); yield* task.halt(); - expect(timeline).toContain("stop:retained"); + expect(timeline).toContain("stop:saved"); }); }); @@ -340,7 +340,7 @@ describe("Tier RT — Site isolation", () => { // The sibling's block ran through the harness's modifier, not the // factory's, so its watch was recorded normally. expect(timeline).toContain("start:own"); - // And the retained resource is released with the site. + // And the saved resource is released with the site. expect(timeline[timeline.length - 1]).toBe("stop:leaky"); }); }); diff --git a/packages/core/tests/secret-detection.test.ts b/packages/core/tests/secret-detection.test.ts index c9b25f7d0..c5a40be59 100644 --- a/packages/core/tests/secret-detection.test.ts +++ b/packages/core/tests/secret-detection.test.ts @@ -533,15 +533,15 @@ describe("default-on secret detection", () => { expect(Object.values(policy).some((value) => typeof value === "function")).toBe(false); }); - it("rejects a description retained from an earlier execution", function* () { - const retained = yield* observedPolicy(false); + it("rejects a description saved from an earlier execution", function* () { + const saved = yield* observedPolicy(false); // Replaying a genuine public value from a disabled run into a later // enabled one is what a brand alone would not stop, which is why the // private policy never escapes in the first place. const error = yield* rejection( runObserving(CLEAN, new InMemoryStream(), undefined, function* () { - yield* Counterfeit.set(retained); + yield* Counterfeit.set(saved); return yield* secretPolicy(); }), ); @@ -598,7 +598,7 @@ describe("default-on secret detection", () => { }); /** - * The gate scans what a run retained, and a run retains what reached its + * The gate scans what a run saved, and a run retains what reached its * per-exec boundary — after any middleware enclosing the execution, which is * the host's own trusted preprocessing (#441). Both directions follow from * that one fact, and both matter: a host may keep a credential out of the @@ -649,7 +649,7 @@ describe("default-on secret detection", () => { const result = yield* yield* execute({ ...inlineSource(LEAKING), stream: backend }); expect(injections).toBeGreaterThan(0); - // The gate reads the retained text, not the command's, so it catches it. + // The gate reads the saved text, not the command's, so it catches it. expect(reportsRejection(result)).toBe(true); expect(persistedText(backend)).not.toContain(CANARY); }); @@ -725,12 +725,12 @@ describe("default-on secret detection", () => { expect(error.message).not.toContain(CANARY); }); - it("leaves no retained value able to scan after the execution ends", function* () { + it("leaves no saved value able to scan after the execution ends", function* () { const backend = new InMemoryStream(); - const retained = yield* runObserving(CLEAN, backend, undefined, secretPolicy); + const saved = yield* runObserving(CLEAN, backend, undefined, secretPolicy); - expect(Reflect.ownKeys(retained)).toEqual(["enabled"]); + expect(Reflect.ownKeys(saved)).toEqual(["enabled"]); const error = yield* rejection(scanSecrets(CANARY)); expect(error.message).not.toContain(CANARY); }); @@ -790,13 +790,13 @@ describe("default-on secret detection", () => { it("leaves nothing usable behind when an execution is halted", function* () { const reached = withResolvers(); - let retained: SecretPolicy | undefined; + let saved: SecretPolicy | undefined; let deferred: Operation | undefined; yield* scoped(function* () { yield* Execution.around({ *document([props], next) { - retained = yield* secretPolicy(); + saved = yield* secretPolicy(); deferred = scanSecrets(CANARY); reached.resolve(); return yield* next(props); @@ -808,8 +808,8 @@ describe("default-on secret detection", () => { yield* reached.operation; }); - expect(retained).toEqual({ enabled: true }); - expect(Reflect.ownKeys(retained ?? {})).toEqual(["enabled"]); + expect(saved).toEqual({ enabled: true }); + expect(Reflect.ownKeys(saved ?? {})).toEqual(["enabled"]); const error = yield* rejection(deferred ?? scanSecrets(CANARY)); expect(error.name).toBe("SecretPolicyUnavailableError"); }); diff --git a/packages/core/tests/shared-operation-permissions.test.ts b/packages/core/tests/shared-operation-permissions.test.ts index 78c39d278..27e1c8216 100644 --- a/packages/core/tests/shared-operation-permissions.test.ts +++ b/packages/core/tests/shared-operation-permissions.test.ts @@ -38,7 +38,7 @@ import { join } from "node:path"; import { collect } from "../src/collect.ts"; import { executeInstalled } from "../host.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import type { ExecutionInstallation } from "../host.ts"; import { selecting } from "./support/generated-selection.ts"; import { registerComponents } from "../src/components/registration.ts"; @@ -232,10 +232,9 @@ function run(fixture: Fixture, source: string): Operation { yield* useHost(fixture); yield* useRegions(); return yield* collect( - yield* executeInstalled( - { ...retainedSource(ROOT_PATH, source), stream: new InMemoryStream() }, - [ceiling()], - ), + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream: new InMemoryStream() }, [ + ceiling(), + ]), ); }); } @@ -470,7 +469,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr return yield* collect( yield* executeInstalled( { - ...retainedSource( + ...savedSource( ROOT_PATH, [ "", @@ -524,7 +523,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr return yield* collect( yield* executeInstalled( { - ...retainedSource( + ...savedSource( ROOT_PATH, `\\n` + `from the fragment\\n'} />\n`, @@ -559,7 +558,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr const rendered = yield* collect( yield* executeInstalled( { - ...retainedSource( + ...savedSource( ROOT_PATH, `\\n` + `\\n'} />\n`, @@ -600,7 +599,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr yield* collect( yield* executeInstalled( { - ...retainedSource( + ...savedSource( ROOT_PATH, `\\n` + `\\n'} />\n`, @@ -661,7 +660,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr // request goes out. yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, source), stream: new InMemoryStream() }, + { ...savedSource(ROOT_PATH, source), stream: new InMemoryStream() }, [admitting], ), ); @@ -681,7 +680,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr try { yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, source), stream: new InMemoryStream() }, + { ...savedSource(ROOT_PATH, source), stream: new InMemoryStream() }, [admitting], ), ); @@ -737,7 +736,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr yield* collect( yield* executeInstalled( { - ...retainedSource( + ...savedSource( ROOT_PATH, // Permitted first — a generated refusal ends the run, so the // element that proves the entry works has to be the one ahead @@ -797,7 +796,7 @@ describe("Tier PRM — authored, generated and direct operations under one restr yield* collect( yield* executeInstalled( { - ...retainedSource( + ...savedSource( ROOT_PATH, ["", "", element, "", "", "", element, ""].join("\n"), ), diff --git a/packages/core/tests/support/answer-provider.ts b/packages/core/tests/support/answer-provider.ts index 14a591e7d..784d62e2b 100644 --- a/packages/core/tests/support/answer-provider.ts +++ b/packages/core/tests/support/answer-provider.ts @@ -181,7 +181,7 @@ function alternatingAnswer( * * It answers the capture and settles. That is what a provider is *for*: the * profile resolves its name once, before the root import, and seals what it - * retained — so a fragment runs the sealed snapshot and the provider has + * saved — so a fragment runs the sealed snapshot and the provider has * nothing left to supply. It still observes every later import and delegates, * which is the most a handler may do to a generated one. * @@ -205,7 +205,7 @@ export function answerProvider( if (asked !== name) { return yield* next(); } - // Retained *per invocation*, which is what a row keeping a stale one + // Saved *per invocation*, which is what a row keeping a stale one // is keeping: the installation is never handed over, so there is no // stable object for a provider to hold instead. options.retain?.push(request); diff --git a/packages/core/tests/syntax-api.test.ts b/packages/core/tests/syntax-api.test.ts index c1d81c5cf..1d8854ddd 100644 --- a/packages/core/tests/syntax-api.test.ts +++ b/packages/core/tests/syntax-api.test.ts @@ -185,12 +185,12 @@ describe("Tier SA — contextual Syntax", () => { for (const [index, record] of records.entries()) { if (record.type !== "yield" || record.result.status !== "ok") { throw new Error( - `Syntax occurrence ${index + 1} retained an unexpected result: ${JSON.stringify(record)}`, + `Syntax occurrence ${index + 1} saved an unexpected result: ${JSON.stringify(record)}`, ); } - expect({ occurrence: index + 1, retained: JSON.stringify(record.result.value) }).toEqual({ + expect({ occurrence: index + 1, saved: JSON.stringify(record.result.value) }).toEqual({ occurrence: index + 1, - retained: expect.stringContaining("FINAL NAMED"), + saved: expect.stringContaining("FINAL NAMED"), }); } }); @@ -286,11 +286,11 @@ describe("Tier SA — contextual Syntax", () => { }); const failed = new InMemoryStream(); yield* run('\n', failed); - const retained = failed + const saved = failed .snapshot() .find((event) => event.type === "yield" && event.description.type === "syntax_symbols"); - expect(retained?.type === "yield" ? retained.result.status : "absent").toBe("err"); - expect(JSON.stringify(retained)).toContain("provider failure"); + expect(saved?.type === "yield" ? saved.result.status : "absent").toBe("err"); + expect(JSON.stringify(saved)).toContain("provider failure"); }); }); diff --git a/packages/core/tests/syntax-component.test.ts b/packages/core/tests/syntax-component.test.ts index f80ee626d..27b902d40 100644 --- a/packages/core/tests/syntax-component.test.ts +++ b/packages/core/tests/syntax-component.test.ts @@ -24,7 +24,7 @@ * ``` * * `{ symbols }` is a successful rendering; `{ refused }` is a named selection - * this component refused, retained as a value rather than as a failure so that a + * this component refused, saved as a value rather than as a failure so that a * continuation reaches the same refusal it reached live. Either way a * continuation answers from the record without consulting the filesystem, the * registry, the bundle or the host again. @@ -59,7 +59,7 @@ import { registerComponents } from "../src/components/registration.ts"; import type { ComponentRegistration } from "../src/components/registration.ts"; import { selectComponent } from "../src/components/select.ts"; import { installedBundle } from "../src/components/bundle.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { renderSyntaxMarkdown } from "../src/syntax-markdown.ts"; import { syntaxReference, rootSyntaxReference } from "../src/syntax-reference.ts"; import type { SyntaxReference } from "../src/syntax-reference.ts"; @@ -211,7 +211,7 @@ function run( return scoped(function* () { return yield* collect( yield* executeInstalled( - { ...retainedSource(ROOT_PATH, source), stream, includes: [...includes] }, + { ...savedSource(ROOT_PATH, source), stream, includes: [...includes] }, [...installations], ), ); @@ -228,7 +228,7 @@ function* refusal(operation: Operation): Operation { throw new Error("expected the operation to be refused"); } -/** Every retained syntax record, in order. */ +/** Every saved syntax record, in order. */ function syntaxReads(events: readonly DurableEvent[]): DurableEvent[] { return events.filter( (event) => event.type === "yield" && event.description.type === "syntax_symbols", @@ -242,7 +242,7 @@ function syntaxReads(events: readonly DurableEvent[]): DurableEvent[] { * says what happened. What must not exist is a *successful* record: that is the * thing a continuation would restore and hand back as the symbols. */ -function retained(events: readonly DurableEvent[]): DurableEvent[] { +function saved(events: readonly DurableEvent[]): DurableEvent[] { return syntaxReads(events).filter( (event) => event.type === "yield" && event.result.status === "ok", ); @@ -260,7 +260,7 @@ function* continuing(stream: InMemoryStream): Operation { return partial; } -/** The same history with one retained reference replaced. */ +/** The same history with one saved reference replaced. */ function* tampered( stream: InMemoryStream, replace: (value: Json) => Json, @@ -501,7 +501,7 @@ describe("Tier SYN — the named form", () => { return yield* collect( yield* executeInstalled( { - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), stream: new InMemoryStream(), includes: [dir], }, @@ -546,7 +546,7 @@ describe("Tier SYN — the named form", () => { return yield* collect( yield* executeReadingAssetsWith( { - ...retainedSource(ROOT_PATH, '\n'), + ...savedSource(ROOT_PATH, '\n'), stream, includes: [], }, @@ -569,7 +569,7 @@ describe("Tier SYN — the named form", () => { // occurrence claimed an identity and no durable operation opened: there is // no record for a continuation to restore, successful or otherwise. expect(syntaxReads(events)).toHaveLength(0); - expect(retained(events)).toHaveLength(0); + expect(saved(events)).toHaveLength(0); }); it("SYN25g: collection snapshots a contribution by value", function* () { @@ -1037,7 +1037,7 @@ describe("Tier SYN — the named form", () => { const inheritedLookups: string[] = []; const localLookups: string[] = []; const observed: unknown[] = []; - const retained: ComponentAnswerRequest[] = []; + const saved: ComponentAnswerRequest[] = []; // The inherited layer's entry into the bootstrap: documentation, a // registration, and the package's provider — which observes and delegates, @@ -1070,7 +1070,7 @@ describe("Tier SYN — the named form", () => { answerProvider("Open", answer.definition, { origin: "test://layer", lookups: localLookups, - retain: retained, + retain: saved, }), ], }; @@ -1106,7 +1106,7 @@ describe("Tier SYN — the named form", () => { // Teardown is the execution's, not the layer's: the request the local // bootstrap's handler was given states nothing once the child run is over, // and the enclosing layers are still standing. - const held = retained[0]; + const held = saved[0]; if (held === undefined) { throw new Error("the local provider was never asked"); } @@ -1288,7 +1288,7 @@ describe("Tier SYN — the named form", () => { return yield* collect( yield* executeReadingAssetsWith( { - ...retainedSource(ROOT_PATH, '\n'), + ...savedSource(ROOT_PATH, '\n'), stream, includes: [], }, @@ -1355,7 +1355,7 @@ describe("Tier SYN — the named form", () => { expect(both).toContain("### ``"); expect(both).toContain("### ``"); - // A continuation restores the retained text without asking again. + // A continuation restores the saved text without asking again. calls.count = 0; const resumed = String( yield* run('\n', [moving], yield* continuing(stream)), @@ -1371,7 +1371,7 @@ describe("Tier SYN — the named form", () => { // Exactly what was rendered, not the compact list: the record is the // occurrence's final text whichever form produced it. - const records = retained(yield* stream.readAll()); + const records = saved(yield* stream.readAll()); expect(records).toHaveLength(1); const record = records[0]; const value = @@ -1400,14 +1400,14 @@ describe("Tier SYN — the named form", () => { const unknown = yield* refusal(run('\n', [], stream)); expect(unknown).toContain("Nonexistent"); - // The refusal is *retained*, and retained as a refusal. This is the one + // The refusal is *saved*, and saved as a refusal. This is the one // record that is not the symbols: an error crossing the durable boundary is // rebuilt without its class, so a refusal that has to mean the same thing // on a replay has to be a value the record distinguishes rather than a // failure the reader re-derives. - const [record] = retained(yield* stream.readAll()); + const [record] = saved(yield* stream.readAll()); if (record?.type !== "yield" || record.result.status !== "ok") { - throw new Error("the refusal retained no record"); + throw new Error("the refusal saved no record"); } expect(Object.keys(Object(record.result.value))).toEqual(["refused"]); // Nothing is handed back as the symbols: a continuation reaches the same @@ -1429,7 +1429,7 @@ describe("Tier SYN — the named form", () => { const each = new InMemoryStream(); const message = yield* refusal(run(`${written}\n`, [], each)); expect([written, message.length > 0]).toEqual([written, true]); - expect([written, retained(yield* each.readAll()).length]).toEqual([written, 0]); + expect([written, saved(yield* each.readAll()).length]).toEqual([written, 0]); } }); }); @@ -1818,7 +1818,7 @@ describe("Tier SYN — the site the symbols describe", () => { const symbols = yield* scoped(function* () { yield* executeInstalled( { - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), stream: new InMemoryStream(), includes: [], }, @@ -1935,7 +1935,7 @@ describe("Tier SYN — the record one occurrence keeps", () => { yield* run("\n", [stating(symbolsOf("Marker")).installation], stream); const [reference] = syntaxReads(yield* stream.readAll()); if (reference?.type !== "yield" || reference.result.status !== "ok") { - throw new Error("the run retained no syntax record"); + throw new Error("the run saved no syntax record"); } const value = Object(reference.result.value); expect(Object.keys(value)).toEqual(["symbols"]); @@ -1970,7 +1970,7 @@ describe("Tier SYN — the record one occurrence keeps", () => { ).toContain("### ``"); }); - it("SYN21: a missing, extra or wrong-typed retained payload refuses before output or binding", function* () { + it("SYN21: a missing, extra or wrong-typed saved payload refuses before output or binding", function* () { const cases: [string, (value: Json) => Json][] = [ ["the member is missing", () => ({})], ["an unknown member was added", (value) => ({ ...Object(value), extra: true })], @@ -2076,12 +2076,12 @@ describe("Tier SYN — reference never permits", () => { // Validation reads the same declaration, so a paired spelling is invalid // before anything runs and the self-closing one is valid. const bad = yield* validateDocumentStructure({ - ...retainedSource("", "content\n"), + ...savedSource("", "content\n"), includes: [], }); expect(bad.diagnostics.some((issue) => issue.code === "invocation-form-invalid")).toBe(true); const good = yield* validateDocumentStructure({ - ...retainedSource("", "\n"), + ...savedSource("", "\n"), includes: [], }); expect(good.diagnostics).toEqual([]); @@ -2300,7 +2300,7 @@ describe("Tier SYN — reference never permits", () => { const output = String( yield* collect( yield* execute({ - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), stream: new InMemoryStream(), includes: [], }), diff --git a/packages/core/tests/syntax-loaded-copy.test.ts b/packages/core/tests/syntax-loaded-copy.test.ts index 4d529ab70..1fa8166e3 100644 --- a/packages/core/tests/syntax-loaded-copy.test.ts +++ b/packages/core/tests/syntax-loaded-copy.test.ts @@ -60,7 +60,7 @@ import { Component } from "../src/component-api.ts"; import { collect } from "../src/collect.ts"; import { executeInstalled } from "../host.ts"; import type { ComponentAnswerInstallation, ExecutionInstallation } from "../host.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import { SYNTAX_COMPONENT, props as syntaxProps } from "../src/components/Syntax.ts"; import { fixedFilesystemDefinition } from "../src/components/fixed-filesystem.ts"; import { form as fileForm, props as fileProps } from "../src/components/File.ts"; @@ -264,7 +264,7 @@ function runRoot( return yield* collect( yield* executeInstalled( { - ...retainedSource("documents/root.md", source), + ...savedSource("documents/root.md", source), stream: new InMemoryStream(), includes: [], }, @@ -399,7 +399,7 @@ describe("Tier FE14 — a separately loaded provider publishes its own answer", return yield* collect( yield* executeInstalled( { - ...retainedSource("documents/root.md", `\\n'} />\n`), + ...savedSource("documents/root.md", `\\n'} />\n`), stream, includes: [], }, @@ -419,11 +419,11 @@ describe("Tier FE14 — a separately loaded provider publishes its own answer", const admission = (yield* stream.readAll()).find( (event) => event.type === "yield" && event.description.type === "generated_xmd", ); - const retained = + const saved = admission?.type === "yield" && admission.result.status === "ok" ? admission.result.value : undefined; - expect(JSON.stringify(retained)).toContain(loaded.identity()); + expect(JSON.stringify(saved)).toContain(loaded.identity()); expect(loaded.identity()).toBe("https://executable.md/components/loaded/read"); }); }); diff --git a/packages/core/tests/temp-dir.test.ts b/packages/core/tests/temp-dir.test.ts index ee8e0b0b8..e68ac4189 100644 --- a/packages/core/tests/temp-dir.test.ts +++ b/packages/core/tests/temp-dir.test.ts @@ -378,9 +378,9 @@ describe("Tier TD — TempDir", () => { expect(output.split("PRESENT")[0]).not.toContain("xmd-tempdir-"); }); - // TD11: retained is not permanent. The path is recorded outside the + // TD11: saved is not permanent. The path is recorded outside the // directory so it can still be named after the execution that owned it ends. - it("TD11: a retained directory is removed when the document scope closes", function* () { + it("TD11: a saved directory is removed when the document scope closes", function* () { const dir = yield* useFixture(); yield* writeDocument( dir, diff --git a/packages/core/tests/workflow-component-bundle.test.ts b/packages/core/tests/workflow-component-bundle.test.ts index b6f8d7ce3..0ce46c17c 100644 --- a/packages/core/tests/workflow-component-bundle.test.ts +++ b/packages/core/tests/workflow-component-bundle.test.ts @@ -39,7 +39,7 @@ import { execute } from "../src/execute.ts"; import { executeInstalled } from "../host.ts"; import type { ExecutionInstallation, WorkflowBundleComponent } from "../host.ts"; import { registerComponents } from "../src/components/registration.ts"; -import { retainedSource } from "../src/root-source.ts"; +import { savedSource } from "../src/root-source.ts"; import type { ComponentDefinition, FunctionComponentDefinition } from "../src/types.ts"; const ROOT_PATH = "workflows/loop.md"; @@ -79,14 +79,14 @@ function* run( stream: InMemoryStream = new InMemoryStream(), ): Operation { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream, includes: [] }, [ + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream, includes: [] }, [ installation(components), ...extra, ]), ); } -/** Whether one retained event is a component import. */ +/** Whether one saved event is a component import. */ function isImport(event: DurableEvent): boolean { return event.type === "yield" && event.description.type === "import_component"; } @@ -126,7 +126,7 @@ function* attempted( ): Operation<{ readonly failure: string; readonly output: string }> { const execution = yield* executeInstalled( { - ...retainedSource(ROOT_PATH, source), + ...savedSource(ROOT_PATH, source), stream: new InMemoryStream(), includes: [], }, @@ -174,7 +174,7 @@ describe("Tier WB — a bundled name resolves to its pinned source", () => { // WB3's `` is form-insensitive, so it cannot show this: a // form-sensitive core component's invocation is bound to the exact definition - // object expansion invokes, and under a bundle that object is the retained + // object expansion invokes, and under a bundle that object is the saved // copy closed authorization answers with — not the one the resolver produced. // A selection recorded against only the resolver's object leaves a genuine // bundled `` refused as unselected. @@ -502,7 +502,7 @@ describe("Tier WB — public import middleware cannot widen a bundle", () => { }); return yield* collect( yield* execute({ - ...retainedSource(ROOT_PATH, "\n"), + ...savedSource(ROOT_PATH, "\n"), stream: new InMemoryStream(), includes: [], }), diff --git a/packages/durable-streams/README.md b/packages/durable-streams/README.md index 3529e33df..e89be9508 100644 --- a/packages/durable-streams/README.md +++ b/packages/durable-streams/README.md @@ -55,7 +55,7 @@ interface Yield { } ``` -**`Close`** is written when a coroutine terminates — whether it completed, threw an error, or was cancelled — after its retained subtree is aligned. Alignment requires every retained yield to be consumed and every retained child coroutine to be claimed by the current definition. Close events are load-bearing: they tell the runtime on restart which coroutines finished cleanly and which need re-execution. An unaligned termination is divergence and appends no `Close`. +**`Close`** is written when a coroutine terminates — whether it completed, threw an error, or was cancelled — after its saved subtree is aligned. Alignment requires every saved yield to be consumed and every saved child coroutine to be claimed by the current definition. Close events are load-bearing: they tell the runtime on restart which coroutines finished cleanly and which need re-execution. An unaligned termination is divergence and appends no `Close`. ### What goes into the journal @@ -331,11 +331,11 @@ During replay, every yielded effect is validated against its journal entry. Only Three additional terminal conditions are checked: - **Generator finishes early**: the code returns before consuming all journal entries — effects were removed. -- **Generator fails early**: the code throws before consuming all journal entries — the ordinary failure cannot close over retained effects the current execution never reached. +- **Generator fails early**: the code throws before consuming all journal entries — the ordinary failure cannot close over saved effects the current execution never reached. - **Generator continues past close**: the journal shows the coroutine closed, but the code keeps yielding — effects were added. -- **Completed child is abandoned**: retained child history has a `Close`, but the current definition never claims that coroutine identity. +- **Completed child is abandoned**: saved child history has a `Close`, but the current definition never claims that coroutine identity. -All indicate the code has changed in a way that makes the stored history invalid. Early return raises `EarlyReturnDivergenceError`; early failure raises `TerminalDivergenceError` with the ordinary failure as its cause. Neither appends a terminal `Close`, so a compatible definition can still replay the retained history. The solution for intentional code changes is `versionCheck`: +All indicate the code has changed in a way that makes the stored history invalid. Early return raises `EarlyReturnDivergenceError`; early failure raises `TerminalDivergenceError` with the ordinary failure as its cause. Neither appends a terminal `Close`, so a compatible definition can still replay the saved history. The solution for intentional code changes is `versionCheck`: ```typescript function* orderWorkflow(orderId: string): Workflow { @@ -381,9 +381,9 @@ The canonical example is a file-backed effect. If the workflow previously read ` A replay guard has three stages, separated by a strict I/O boundary: -**Stage 1 — `check`**: runs in generator context before replay begins, once per retained `Yield`. I/O is allowed. Use it to gather current state (compute file hashes, check timestamps) and cache results in the middleware closure. +**Stage 1 — `check`**: runs in generator context before replay begins, once per saved `Yield`. I/O is allowed. Use it to gather current state (compute file hashes, check timestamps) and cache results in the middleware closure. -**Stage 2 — `admit`**: runs once after every retained event has been offered to `check`, and before a recorded terminal result is reused. It receives the retained history as a whole — the coroutine about to be resumed, its retained `Yield`s, and whether a terminal result exists for it. A guard that requires something of the history *as a whole* — that an event it validates is present at all, and present once — refuses here, because a per-event `check` has nothing to object to in a journal that simply omits the event. The default is a no-op. +**Stage 2 — `admit`**: runs once after every saved event has been offered to `check`, and before a recorded terminal result is reused. It receives the saved history as a whole — the coroutine about to be resumed, its saved `Yield`s, and whether a terminal result exists for it. A guard that requires something of the history *as a whole* — that an event it validates is present at all, and present once — refuses here, because a per-event `check` has nothing to object to in a journal that simply omits the event. The default is a no-op. **Stage 3 — `decide`**: runs synchronously inside the replay loop, after identity matching succeeds. Must be pure — no I/O, no side effects. Reads from the cache populated during `check` and returns a `ReplayOutcome`. @@ -393,11 +393,11 @@ The separation between generator and synchronous stages is necessary because the A replay distinguishes three things, and conflating any two of them gives the wrong holder the decision: -1. **The authoritative retained history.** What a consumer's own admission validated and what replay consumes. Detached from the backend and immutable to policy — its descriptions and results are frozen through, so nothing that receives it can rewrite what replay will decide. +1. **The authoritative saved history.** What a consumer's own admission validated and what replay consumes. Detached from the backend and immutable to policy — its descriptions and results are frozen through, so nothing that receives it can rewrite what replay will decide. 2. **Isolated guard observations.** What `check`, `admit`, and `decide` receive: a deep, mutable copy made per invocation. Middleware may read, annotate, and compose over it freely; nothing it writes reaches replay. -3. **Values delivered to workflow code.** A fresh mutable copy taken from the retained history at the moment of consumption — for a replayed effect and for a completed run's own return value alike. This is the only thing "mutable replayed value" ever means: a document that resumes on a restored binding writes to its copy, and writing to it cannot reach the retained history or the next replay. +3. **Values delivered to workflow code.** A fresh mutable copy taken from the saved history at the moment of consumption — for a replayed effect and for a completed run's own return value alike. This is the only thing "mutable replayed value" ever means: a document that resumes on a restored binding writes to its copy, and writing to it cannot reach the saved history or the next replay. -The retained history is frozen through in **every** settlement shape — a success with a value, a `Result` with none, a failure, and a cancellation, on `Yield` and `Close` alike. An envelope left writable is one a public observation could add a value to before replay reads it. +The saved history is frozen through in **every** settlement shape — a success with a value, a `Result` with none, a failure, and a cancellation, on `Yield` and `Close` alike. An envelope left writable is one a public observation could add a value to before replay reads it. Handing policy the authoritative events would let a guard rename effect A to B — so a workflow asking for B consumes A's result without B ever running — or rewrite a recorded root selection after admission accepted it. @@ -409,21 +409,21 @@ That makes a guard the wrong place for anything that decides *identity*. A consu Use guards for staleness policy. Do not use them to enforce an invariant that must not be negotiable. -### Retained events are read once +### Saved events are read once -Every phase of a replay reads the same events, and a journal is data a backend supplies. Events are therefore **retained**: read once and detached from whatever the backend still owns. +Every phase of a replay reads the same events, and a journal is data a backend supplies. Events are therefore **saved**: read once and detached from whatever the backend still owns. -**Every** event that participates in admission, indexing, or terminal reuse is retained — `Close` as well as `Yield`. A `Close` decides whether a coroutine has a terminal result to reuse, so leaving it as the backend's own object lets it belong to a child coroutine while one phase asks and to the root while the next does. +**Every** event that participates in admission, indexing, or terminal reuse is saved — `Close` as well as `Yield`. A `Close` decides whether a coroutine has a terminal result to reuse, so leaving it as the backend's own object lets it belong to a child coroutine while one phase asks and to the root while the next does. -The **discriminator** is settled by the classification that chooses an event's retained kind, and never read from the source again. **Identity** — the coroutine an event belongs to, and a `Yield`'s complete effect description — is settled once too, so no phase can be shown a different event than the phase before it. An event that refuses to say what it is is refused from every member. +The **discriminator** is settled by the classification that chooses an event's saved kind, and never read from the source again. **Identity** — the coroutine an event belongs to, and a `Yield`'s complete effect description — is settled once too, so no phase can be shown a different event than the phase before it. An event that refuses to say what it is is refused from every member. A `Yield`'s **settlement** stays lazy and separate, because the index is built before guards run and a guard that would refuse an event must get that chance before the stream is asked to produce a result. -A `Close`'s result is settled **while the history is retained**, not at a first later read. A `Close` carries what a completed run hands back, and deferring that read leaves an interval — between the moment a consumer's own admission accepts the history and the moment terminal reuse consumes it — in which the backend still owns the answer and can replace it. Reading once at a later getter closes repeated reads and leaves that window open. +A `Close`'s result is settled **while the history is saved**, not at a first later read. A `Close` carries what a completed run hands back, and deferring that read leaves an interval — between the moment a consumer's own admission accepts the history and the moment terminal reuse consumes it — in which the backend still owns the answer and can replace it. Reading once at a later getter closes repeated reads and leaves that window open. Every cell keeps both outcomes: a refusal is remembered and re-raised rather than retried, so retaining a history never fails and a refusal reaches whichever phase asks. -A retained event presents its members as ordinary own properties, so it spreads, serializes, and compares like the plain event a backend would have supplied. +A saved event presents its members as ordinary own properties, so it spreads, serializes, and compares like the plain event a backend would have supplied. A detached result shares no object or array with the journal, and remains ordinary mutable JSON — detaching is a claim against the *stream*, not against the consumer, and replayed values are legitimately written to. @@ -654,7 +654,7 @@ members replay guards read. Nothing from the record reaches the failure. Members the protocol does not name appear in the path as `*` rather than by name, because a record's own member -names are as much retained content as its values, so a parse failure copies +names are as much saved content as its values, so a parse failure copies neither into logs and terminals. --- @@ -719,7 +719,7 @@ import { preserveJournalProvenance, } from "@executablemd/durable-streams"; -const provenance = establishJournalProvenance(journal); // retained by the provider +const provenance = establishJournalProvenance(journal); // saved by the provider ``` The value is a `JournalProvenance` — a non-operational, equality-only witness. diff --git a/packages/durable-streams/combinators.ts b/packages/durable-streams/combinators.ts index 8cded6bf9..24c718e04 100644 --- a/packages/durable-streams/combinators.ts +++ b/packages/durable-streams/combinators.ts @@ -113,7 +113,7 @@ function* runDurableChild( unaligned.cursor, unaligned.totalYields, { - message: `Divergence: coroutine ${childId} was cancelled before retained history was exhausted`, + message: `Divergence: coroutine ${childId} was cancelled before saved history was exhausted`, unconsumed: unaligned.entry, }, ); diff --git a/packages/durable-streams/context.ts b/packages/durable-streams/context.ts index 16e332209..1ff54c651 100644 --- a/packages/durable-streams/context.ts +++ b/packages/durable-streams/context.ts @@ -32,7 +32,7 @@ export interface DurableContext { /** * How many durable yields this coroutine has settled. * - * Advanced by both settlement paths, so a coroutine running past its retained + * Advanced by both settlement paths, so a coroutine running past its saved * history keeps counting where replay left off. See `position.ts`. */ position?: number; diff --git a/packages/durable-streams/effect.ts b/packages/durable-streams/effect.ts index 81ad75681..12426b93d 100644 --- a/packages/durable-streams/effect.ts +++ b/packages/durable-streams/effect.ts @@ -36,11 +36,11 @@ import { type LiveDurableOperationCoordinator, } from "./live-coordinator.ts"; import { ReplayGuard } from "./replay-guard.ts"; -import { consumable, observeEvent } from "./retained.ts"; +import { consumable, observeEvent } from "./saved.ts"; import { protocolToEffection, serializeError } from "./serialize.ts"; import { advanceDurablePosition } from "./position.ts"; import type { YieldEntry } from "./replay-index.ts"; -import type { AbandonedRetainedHistory } from "./live-coordinator.ts"; +import type { AbandonedSavedHistory } from "./live-coordinator.ts"; import type { CoroutineView, DurableEffect, @@ -84,7 +84,7 @@ type ReplayResult = | { path: "live"; /** - * The retained entry this live path stepped over, when it stepped over + * The saved entry this live path stepped over, when it stepped over * one. * * Present only when replay had an entry at this position and divergence @@ -94,7 +94,7 @@ type ReplayResult = * all, and reads it from here rather than from anything a document can * supply. */ - abandoned: AbandonedRetainedHistory; + abandoned: AbandonedSavedHistory; }; /** @@ -151,7 +151,7 @@ function checkReplay( // Description matches — now check replay guards before replaying. // An isolated observation, like the check and admit phases: a decision is - // policy, and policy reads. Handing the retained description or result + // policy, and policy reads. Handing the saved description or result // here would let a guard rewrite what replay is about to consume. const observed = observeEvent({ type: "yield", diff --git a/packages/durable-streams/errors.ts b/packages/durable-streams/errors.ts index c22098912..d499123c2 100644 --- a/packages/durable-streams/errors.ts +++ b/packages/durable-streams/errors.ts @@ -57,7 +57,7 @@ function isCoordinate(value: Json | undefined, least: number): value is number { } /** - * The terminal-divergence message, naming the first retained entry the + * The terminal-divergence message, naming the first saved entry the * terminating subtree did not reach when one was selected. */ function withUnreached(message: string, unconsumed: Yield | Close | undefined): string { @@ -90,9 +90,9 @@ export class DurablePersistenceError extends Error { * * `path` locates the offending member within the record, such as * `$.result.error.message`. Members the protocol does not name appear as `*`, - * because a record's own member names are as much retained content as its + * because a record's own member names are as much saved content as its * values. Neither the path nor the message repeats anything from the record: a - * journal is retained, filtered history, and a parse failure is not a reason to + * journal is saved, filtered history, and a parse failure is not a reason to * copy its contents into an error that travels to logs and terminals. */ export class MalformedDurableEventError extends Error { @@ -147,7 +147,7 @@ export class DivergenceError extends Error { /** * Raised when a workflow terminates while replay still has unconsumed entries. - * The retained journal describes effects that the current execution did not + * The saved journal describes effects that the current execution did not * reach, so no terminal Close may be appended over that history. */ export class TerminalDivergenceError extends Error { @@ -156,7 +156,7 @@ export class TerminalDivergenceError extends Error { coroutineId: CoroutineId; consumedCount: number; totalCount: number; - /** The first retained entry the terminating subtree did not reach. */ + /** The first saved entry the terminating subtree did not reach. */ unconsumed?: Yield | Close; constructor( diff --git a/packages/durable-streams/live-coordinator.ts b/packages/durable-streams/live-coordinator.ts index ce37a2db9..c0ea9316d 100644 --- a/packages/durable-streams/live-coordinator.ts +++ b/packages/durable-streams/live-coordinator.ts @@ -9,13 +9,13 @@ export type ActivateDurabilityFailure = (failure: unknown) => Error; /** * What replay left behind at this position, when it left anything. * - * Present only when a retained entry was there and divergence policy chose to + * Present only when a saved entry was there and divergence policy chose to * run live rather than refuse. It is the engine's own account of the position, * so an effect whose live work reaches outside this journal can decide whether * running live here is something it may do — without asking anything a document * supplies. */ -export interface AbandonedRetainedEntry { +export interface AbandonedSavedEntry { readonly description: EffectDescription; readonly result: Result; } @@ -23,15 +23,15 @@ export interface AbandonedRetainedEntry { /** * What replay left behind in this coroutine, as the engine sees it. * - * `entry` is the retained entry this very operation stepped over, when there + * `entry` is the saved entry this very operation stepped over, when there * was one. `steppedOver` is the wider fact and the load-bearing one: it stays * true for every operation after the one that diverged, because disabling * replay does not consume the history it walked away from. An effect that reads * only `entry` would be safe at the position that diverged and unguarded at * every position after it. */ -export interface AbandonedRetainedHistory { - readonly entry?: AbandonedRetainedEntry; +export interface AbandonedSavedHistory { + readonly entry?: AbandonedSavedEntry; readonly steppedOver: boolean; } @@ -42,7 +42,7 @@ export interface LiveDurableOperationCoordinator { publish: (result: Result) => Operation, activateFailure: ActivateDurabilityFailure, journalProvenance: JournalProvenance | undefined, - abandoned: AbandonedRetainedHistory, + abandoned: AbandonedSavedHistory, ): Operation; } @@ -53,7 +53,7 @@ export const defaultLiveDurableOperationCoordinator: LiveDurableOperationCoordin publish: (result: Result) => Operation, _activateFailure: ActivateDurabilityFailure, _journalProvenance: JournalProvenance | undefined, - _abandoned: AbandonedRetainedHistory, + _abandoned: AbandonedSavedHistory, ): Operation { let result: Result; try { diff --git a/packages/durable-streams/mod.ts b/packages/durable-streams/mod.ts index 03c313c0f..24fde60af 100644 --- a/packages/durable-streams/mod.ts +++ b/packages/durable-streams/mod.ts @@ -27,8 +27,8 @@ export type { export { ReplayIndex } from "./replay-index.ts"; // `retainEvents` is public because `@executablemd/core` owns its own journal // gate and must produce the stable history across the package boundary. The -// retained classes and the detach helpers stay internal. -export { retainEvents } from "./retained.ts"; +// saved classes and the detach helpers stay internal. +export { retainEvents } from "./saved.ts"; export type { YieldEntry } from "./replay-index.ts"; // Stream interface @@ -64,8 +64,8 @@ export type { DivergenceDecision, DivergenceInfo, DivergenceKind } from "./diver // ReplayGuard API — pluggable validation for replay staleness detection export { ReplayGuard } from "./replay-guard.ts"; -export type { ReplayOutcome, RetainedHistory } from "./replay-guard.ts"; -export type { AbandonedRetainedEntry, AbandonedRetainedHistory } from "./live-coordinator.ts"; +export type { ReplayOutcome, SavedHistory } from "./replay-guard.ts"; +export type { AbandonedSavedEntry, AbandonedSavedHistory } from "./live-coordinator.ts"; // Context export { DurableContext } from "./context.ts"; diff --git a/packages/durable-streams/parse.ts b/packages/durable-streams/parse.ts index 0747bfdcb..c629f7b2e 100644 --- a/packages/durable-streams/parse.ts +++ b/packages/durable-streams/parse.ts @@ -100,7 +100,7 @@ function parseEffectDescription(value: unknown, path: string): EffectDescription continue; } // `${path}.*`, not `${path}.${key}`: these member names come from the - // record, and a record's own names are as much retained content as its + // record, and a record's own names are as much saved content as its // values. A path built from one would carry it into logs and terminals. define(description, key, parseJsonValue(member, `${path}.*`)); } @@ -202,7 +202,7 @@ function parseStringMember(members: Members, key: string, path: string): string * * The names it *does* declare are this module's own and safe to print; the one * that turned up is content from the record, and a record's member names are - * as much retained history as its values. + * as much saved history as its values. */ function requireMemberNames(members: Members, names: string[], path: string): void { for (const key of members.keys()) { diff --git a/packages/durable-streams/position.ts b/packages/durable-streams/position.ts index 29b4081e9..5a8c59388 100644 --- a/packages/durable-streams/position.ts +++ b/packages/durable-streams/position.ts @@ -3,7 +3,7 @@ * Operations without an authored name can use this to identify their position. * * The count advances on replayed yields and committed live yields. The replay - * cursor alone stops at the retained prefix and would collide for every + * cursor alone stops at the saved prefix and would collide for every * operation in a live suffix. * * This is derived observation, not durable state or permission. Decisions that diff --git a/packages/durable-streams/replay-guard.ts b/packages/durable-streams/replay-guard.ts index fb08fd2c4..cb3ce414b 100644 --- a/packages/durable-streams/replay-guard.ts +++ b/packages/durable-streams/replay-guard.ts @@ -26,7 +26,7 @@ * observation-gathering happens. Results are cached in middleware closures. * * 2. **admit** (after every check, before terminal reuse): Runs once with the - * retained history as a whole. A guard that requires something of the + * saved history as a whole. A guard that requires something of the * history rather than of one event — that an event it validates is present, * and present once — refuses here, because a per-event check has nothing to * object to in a journal that omits the event. Default is a no-op. @@ -48,7 +48,7 @@ import { createApi } from "effection/experimental"; import type { CoroutineId, Yield } from "./types.ts"; /** - * The retained history a run is about to replay, offered once. + * The saved history a run is about to replay, offered once. * * A guard's per-event check can only speak about events a journal contains. A * journal missing something a guard requires offers it nothing to object to, @@ -56,10 +56,10 @@ import type { CoroutineId, Yield } from "./types.ts"; * that was never validated. This is where a guard says whether the history as a * whole may be replayed at all. */ -export interface RetainedHistory { +export interface SavedHistory { /** The coroutine whose recorded terminal result is about to be reused. */ readonly coroutineId: CoroutineId; - /** Every retained Yield, each owning the one cell for what it settled to. */ + /** Every saved Yield, each owning the one cell for what it settled to. */ readonly yields: readonly Yield[]; /** Whether a recorded terminal result exists for that coroutine. */ readonly terminal: boolean; @@ -92,12 +92,12 @@ interface ReplayGuardApi { /** Phase 1: Check — gather observations before replay (I/O allowed). */ check(event: Yield): Operation; /** - * Phase 1b: Admit — the retained history has been offered in full, and a + * Phase 1b: Admit — the saved history has been offered in full, and a * recorded terminal result has not been reused yet. A guard that requires * something of the history as a whole — that an event it validates is * present at all, and present once — refuses here by throwing. */ - admit(history: RetainedHistory): Operation; + admit(history: SavedHistory): Operation; /** Phase 2: Decide — return replay outcome (synchronous, pure). */ decide(event: Yield): ReplayOutcome; } @@ -112,7 +112,7 @@ function* defaultCheck(_event: Yield): Operation { /** * Default admit — no-op. A history nobody objects to is replayed. */ -function* defaultAdmit(_history: RetainedHistory): Operation { +function* defaultAdmit(_history: SavedHistory): Operation { // No requirement — pass through to next middleware or default. } diff --git a/packages/durable-streams/replay-index.ts b/packages/durable-streams/replay-index.ts index d22df10e4..0cad7bd6c 100644 --- a/packages/durable-streams/replay-index.ts +++ b/packages/durable-streams/replay-index.ts @@ -5,7 +5,7 @@ * to Close events. See spec §4.1. */ -import { retainEvents } from "./retained.ts"; +import { retainEvents } from "./saved.ts"; import type { Close, CoroutineId, @@ -22,19 +22,19 @@ export interface YieldEntry { export class ReplayIndex { private yields = new Map(); - /** Every retained Yield in stream order, each owning its own settled cells. */ - private retained: Yield[] = []; + /** Every saved Yield in stream order, each owning its own settled cells. */ + private saved: Yield[] = []; private cursors = new Map(); private closes = new Map(); /** Coroutines where replay has been disabled (run-live mode). */ private disabled = new Set(); - /** Retained coroutine identities reached by the current definition. */ + /** Saved coroutine identities reached by the current definition. */ private claimed = new Set(); /** * Index a journal's events by identity, without reading what they settled to. * - * The events are retained first — idempotently, so a caller that already + * The events are saved first — idempotently, so a caller that already * produced the stable history hands the same objects on rather than a second * wrapping of them, and every phase then observes one identity and one * settlement per event. @@ -47,7 +47,7 @@ export class ReplayIndex { list = []; this.yields.set(event.coroutineId, list); } - this.retained.push(event); + this.saved.push(event); list.push(event); } if (event.type === "close") { @@ -57,13 +57,13 @@ export class ReplayIndex { } /** - * Every retained Yield in stream order, as the events a check phase sees. + * Every saved Yield in stream order, as the events a check phase sees. * * The same objects the replay path consumes, so a guard and a later consumer * observe one settled result rather than two reads of the stream. */ - retainedYields(): Yield[] { - return [...this.retained]; + savedYields(): Yield[] { + return [...this.saved]; } /** @@ -82,17 +82,17 @@ export class ReplayIndex { return this.disabled.has(coroutineId); } - /** Mark a retained coroutine identity as reached by the current run. */ + /** Mark a saved coroutine identity as reached by the current run. */ claim(coroutineId: CoroutineId): void { this.claimed.add(coroutineId); if (!this.closes.has(coroutineId)) { return; } - const retainedIds = new Set([...this.yields.keys(), ...this.closes.keys()]); - for (const retainedId of retainedIds) { - if (retainedId.startsWith(`${coroutineId}.`)) { - this.claimed.add(retainedId); + const savedIds = new Set([...this.yields.keys(), ...this.closes.keys()]); + for (const savedId of savedIds) { + if (savedId.startsWith(`${coroutineId}.`)) { + this.claimed.add(savedId); } } } @@ -117,7 +117,7 @@ export class ReplayIndex { } /** - * Whether this coroutine walked away from retained history it never consumed. + * Whether this coroutine walked away from saved history it never consumed. * * True once replay has been disabled for it while entries it had not reached * are still there — which is what a run-live decision leaves behind. It stays @@ -178,9 +178,9 @@ export class ReplayIndex { } /** - * Return the first retained coroutine not aligned with the current subtree. + * Return the first saved coroutine not aligned with the current subtree. * - * `entry` is the first unmatched retained protocol entry itself — the Yield + * `entry` is the first unmatched saved protocol entry itself — the Yield * at the cursor, or for an unmatched completed child its first Yield when it * has one and otherwise its Close — so a terminal diagnostic can name what * the terminating subtree did not reach, not just where it stopped. diff --git a/packages/durable-streams/run.ts b/packages/durable-streams/run.ts index be8f2bb21..91173b96b 100644 --- a/packages/durable-streams/run.ts +++ b/packages/durable-streams/run.ts @@ -19,7 +19,7 @@ import { DurableContext } from "./context.ts"; import { activeDurabilityFailure, appendDurableEvent } from "./durability.ts"; import { EarlyReturnDivergenceError, TerminalDivergenceError } from "./errors.ts"; import { ReplayGuard } from "./replay-guard.ts"; -import { consumable, observeEvent } from "./retained.ts"; +import { consumable, observeEvent } from "./saved.ts"; import { ReplayIndex } from "./replay-index.ts"; import { deserializeError, serializeError } from "./serialize.ts"; import type { DurableStream } from "./stream.ts"; @@ -38,7 +38,7 @@ function unalignedReplay(replayIndex: ReplayIndex, coroutineId: string) { * results for the decide phase. * * The events come from the index rather than from the stream, so a guard reads - * the same retained result the replay path will use. Handing over the stream's + * the same saved result the replay path will use. Handing over the stream's * own events instead would make validation and consumption two separate reads, * and a source that answered differently between them could have a guard * approve one result while execution used another. @@ -46,8 +46,8 @@ function unalignedReplay(replayIndex: ReplayIndex, coroutineId: string) { * See replay-guard-spec.md §5.5. */ function* runCheckPhase(replayIndex: ReplayIndex, scope: Scope): Operation { - for (const event of replayIndex.retainedYields()) { - // An isolated observation, not the retained event. Guards compose by + for (const event of replayIndex.savedYields()) { + // An isolated observation, not the saved event. Guards compose by // reading and passing along; what composition must not become is the power // to edit a history the execution already validated. const observed = observeEvent(event); @@ -75,7 +75,7 @@ export interface DurableRunOptions { * 3. Runs the workflow — replayed effects resolve synchronously from * the index; live effects execute and persist before resuming. * 4. On completion, appends a Close event to the stream. - * 5. Before any Close, rejects durability failures and retained coroutine + * 5. Before any Close, rejects durability failures and saved coroutine * history the current definition did not align with. * * Returns the workflow's result value. @@ -115,14 +115,14 @@ export function* durableRun( // See replay-guard-spec.md §5.5. yield* runCheckPhase(replayIndex, scope); - // The retained history has been offered in full and nothing has been reused + // The saved history has been offered in full and nothing has been reused // yet. A guard that requires something of the history as a whole — that an // event it validates is present, and present once — refuses here, before the // recorded terminal result below can answer for history nobody validated. yield* ReplayGuard.invoke(scope, "admit", [ { coroutineId, - yields: replayIndex.retainedYields().flatMap((event) => { + yields: replayIndex.savedYields().flatMap((event) => { const observed = observeEvent(event); return observed.type === "yield" ? [observed] : []; }), @@ -137,7 +137,7 @@ export function* durableRun( const closeEvent = replayIndex.getClose(coroutineId)!; if (closeEvent.result.status === "ok") { // A fresh consumer copy, exactly as a replayed Yield's result is. The - // retained settlement is frozen so policy cannot rewrite it; what a + // saved settlement is frozen so policy cannot rewrite it; what a // caller receives from a completed run is ordinary data it may hold and // change, and changing it cannot reach the next replay. const settled = consumable(closeEvent.result); diff --git a/packages/durable-streams/retained.ts b/packages/durable-streams/saved.ts similarity index 83% rename from packages/durable-streams/retained.ts rename to packages/durable-streams/saved.ts index f07d18bbe..631fd0055 100644 --- a/packages/durable-streams/retained.ts +++ b/packages/durable-streams/saved.ts @@ -1,5 +1,5 @@ /** - * Retained events — what a run reads a journal as. + * Saved events — what a run reads a journal as. * * A journal is data supplied by a backend, and every phase of a replay reads * the same events: a private admission gate, the replay index, public guard @@ -8,14 +8,14 @@ * one thing for validation and another for execution, and nothing downstream * can detect the substitution. * - * A retained event is therefore read once and detached. **Every** event that - * participates in admission, indexing, or terminal reuse is retained, Close as + * A saved event is therefore read once and detached. **Every** event that + * participates in admission, indexing, or terminal reuse is saved, Close as * well as Yield: a Close decides whether a coroutine has a terminal result to * reuse, so leaving it as the backend's own object lets it belong to a child * coroutine while one phase asks and to the root while the next does. * * The discriminator is settled once, by the classification that chooses a - * retained event's kind, and never read from the source again. Identity — the + * saved event's kind, and never read from the source again. Identity — the * coroutine an event belongs to, and a Yield's complete effect description — is * settled once too, so no phase can be shown a different event than the phase * before it. @@ -37,7 +37,7 @@ import type { Yield, } from "./types.ts"; -/** What one retained value's single read of the stream produced. */ +/** What one saved value's single read of the stream produced. */ type Settled = | { readonly kind: "value"; readonly value: T } | { readonly kind: "refusal"; readonly refusal: unknown }; @@ -58,7 +58,7 @@ function resolve(settled: Settled): T { } /** - * A detached copy of one retained JSON value. + * A detached copy of one saved JSON value. * * Every property is read once and rebuilt, so nothing the stream still owns * remains reachable: a nested accessor cannot answer one thing to one phase and @@ -83,7 +83,7 @@ export function detachJson(value: Json, seen: Set = new Set()): Json { return value; } if (seen.has(value)) { - throw new TypeError("a retained value cannot contain a cycle"); + throw new TypeError("a saved value cannot contain a cycle"); } seen.add(value); try { @@ -112,7 +112,7 @@ export function detachJson(value: Json, seen: Set = new Set()): Json { /** * A detached copy, frozen through. * - * The authoritative retained graph is what admission validated and what replay + * The authoritative saved graph is what admission validated and what replay * consumes, so nothing that reaches a caller may write to it. Freezing is the * restriction on *policy*, not on a workflow: what a document finally receives * is a fresh mutable copy taken from this, never this. @@ -133,7 +133,7 @@ function freezeDeep(value: Json): void { } } -/** A detached copy of a retained failure's description. */ +/** A detached copy of a saved failure's description. */ function detachError(error: SerializedError): SerializedError { const name = error.name; const stack = error.stack; @@ -145,7 +145,7 @@ function detachError(error: SerializedError): SerializedError { } /** - * A retained result detached from everything the stream still owns. + * A saved result detached from everything the stream still owns. * * Each member is read exactly once, here, and the tree beneath it is rebuilt. * Copying only the outer object would leave a `value` the journal can still @@ -156,7 +156,7 @@ function detachResult(result: Result): Result { if (status === "ok") { // Every successful shape, including the one that settled to nothing. A // `Result` carries no value to detach, but the envelope is still - // the retained settlement — left writable, a caller of a public observation could add one + // the saved settlement — left writable, a caller of a public observation could add one // before replay reads it. if (!("value" in result)) { return Object.freeze({ status }); @@ -166,7 +166,7 @@ function detachResult(result: Result): Result { } if (status === "err") { if (!("error" in result)) { - throw new TypeError("a retained failure carries the error it failed with"); + throw new TypeError("a saved failure carries the error it failed with"); } return Object.freeze({ status, error: detachError(result.error) }); } @@ -200,7 +200,7 @@ function detachDescription(description: EffectDescription): EffectDescription { extra.push([key, member]); } if (typeof type !== "string" || typeof name !== "string") { - throw new TypeError("a retained effect description carries a type and a name"); + throw new TypeError("a saved effect description carries a type and a name"); } const detached: EffectDescription = { type, name }; for (const [key, member] of extra) { @@ -214,8 +214,8 @@ function detachDescription(description: EffectDescription): EffectDescription { return Object.freeze(detached); } -/** Everything about a retained Yield except what it settled to. */ -interface RetainedIdentity { +/** Everything about a saved Yield except what it settled to. */ +interface SavedIdentity { readonly coroutineId: CoroutineId; readonly description: EffectDescription; } @@ -223,12 +223,12 @@ interface RetainedIdentity { function readCoroutineId(source: { coroutineId: CoroutineId }): CoroutineId { const coroutineId = source.coroutineId; if (typeof coroutineId !== "string") { - throw new TypeError("a retained event belongs to a coroutine"); + throw new TypeError("a saved event belongs to a coroutine"); } return coroutineId; } -function readIdentity(source: Yield): RetainedIdentity { +function readIdentity(source: Yield): SavedIdentity { return { coroutineId: readCoroutineId(source), description: detachDescription(source.description), @@ -236,7 +236,7 @@ function readIdentity(source: Yield): RetainedIdentity { } /** - * One retained Yield: one identity, and one cell for what it settled to. + * One saved Yield: one identity, and one cell for what it settled to. * * Identity is read together and once. Reading `type` here and `coroutineId` * there would let a source present an unrelated event to one phase and the root @@ -250,9 +250,9 @@ function readIdentity(source: Yield): RetainedIdentity { * refuse one phase and then answer the next. */ /** - * Present a retained member the way the event it stands for presents it. + * Present a saved member the way the event it stands for presents it. * - * Own and enumerable, so a retained event spreads, serializes, and compares + * Own and enumerable, so a saved event spreads, serializes, and compares * like the plain event a backend would have supplied. The settled cells stay * genuinely private, which is what keeps them out of all of that. */ @@ -260,7 +260,7 @@ function present(target: object, name: string, read: () => T): void { Object.defineProperty(target, name, { enumerable: true, get: read }); } -class RetainedYield implements Yield { +class SavedYield implements Yield { /** * Settled by the classification that chose this wrapper, never re-read. A * second read of the source's own discriminator is a second chance for it to @@ -272,7 +272,7 @@ class RetainedYield implements Yield { declare readonly description: EffectDescription; declare readonly result: Result; #source: Yield; - #identity: Settled | undefined; + #identity: Settled | undefined; #settled: Settled | undefined; constructor(source: Yield) { @@ -286,14 +286,14 @@ class RetainedYield implements Yield { }); } - #stable(): RetainedIdentity { + #stable(): SavedIdentity { this.#identity ??= settle(() => readIdentity(this.#source)); return resolve(this.#identity); } } /** - * One retained Close: settled identity, and one cell for its terminal result. + * One saved Close: settled identity, and one cell for its terminal result. * * A Close decides whether a coroutine has a terminal result to reuse, so it * participates in admission exactly as a Yield does. Left as the backend's own @@ -301,7 +301,7 @@ class RetainedYield implements Yield { * root while the next does — a history nobody could admit, reused as a result * nobody asked for. */ -class RetainedClose implements Close { +class SavedClose implements Close { declare readonly type: "close"; declare readonly coroutineId: CoroutineId; declare readonly result: Result; @@ -309,7 +309,7 @@ class RetainedClose implements Close { #settled: Settled; constructor(source: Close) { - // Settled here, while the history is being retained, rather than at a first + // Settled here, while the history is being saved, rather than at a first // later read. A Close carries the result a completed run hands back, and // deferring that read leaves an interval — between the moment a consumer's // private admission accepts the history and the moment terminal reuse @@ -335,7 +335,7 @@ class RetainedClose implements Close { * remembered and re-raised from every member, rather than retried — a source * that refuses one phase must not answer the next. */ -class RetainedRefusal implements Yield { +class SavedRefusal implements Yield { declare readonly type: "yield"; declare readonly coroutineId: CoroutineId; declare readonly description: EffectDescription; @@ -353,46 +353,44 @@ class RetainedRefusal implements Yield { } /** - * The retained form of a journal's events. + * The saved form of a journal's events. * - * Idempotent: retaining an already-retained event returns it, so a caller that + * Idempotent: retaining an already-saved event returns it, so a caller that * has produced the stable history hands the same objects onward rather than a * second wrapping of them. That is what lets one snapshot serve every phase. * * Only the event type is read here, which is the least a caller can read and - * still tell a Yield from a Close. Everything else is the retained event's own. + * still tell a Yield from a Close. Everything else is the saved event's own. */ export function retainEvents(events: readonly DurableEvent[]): DurableEvent[] { return events.map((event) => { - if (isRetained(event)) { + if (isSaved(event)) { return event; } // The one read of the source's discriminator. Whatever it says here is what - // the retained event reports from now on, to every phase. + // the saved event reports from now on, to every phase. const classified = settle(() => event.type); if (classified.kind === "refusal") { - return new RetainedRefusal(classified.refusal); + return new SavedRefusal(classified.refusal); } if (classified.value === "yield") { - return new RetainedYield(event as Yield); + return new SavedYield(event as Yield); } if (classified.value === "close") { - return new RetainedClose(event as Close); + return new SavedClose(event as Close); } - return new RetainedRefusal(new TypeError("a retained event is a yield or a close")); + return new SavedRefusal(new TypeError("a saved event is a yield or a close")); }); } -function isRetained(event: DurableEvent): boolean { +function isSaved(event: DurableEvent): boolean { return ( - event instanceof RetainedYield || - event instanceof RetainedClose || - event instanceof RetainedRefusal + event instanceof SavedYield || event instanceof SavedClose || event instanceof SavedRefusal ); } /** - * An isolated observation of a retained event, for public policy to read. + * An isolated observation of a saved event, for public policy to read. * * A replay guard is composable policy, and composition means handlers read, * annotate, and pass along. What it must never mean is that a handler edits the @@ -433,11 +431,11 @@ function observeDescription(description: EffectDescription): EffectDescription { } /** - * A retained result as a consumer may hold it: ordinary mutable JSON. + * A saved result as a consumer may hold it: ordinary mutable JSON. * * The authoritative copy is frozen so policy cannot rewrite it. A document * that resumes on a restored binding writes to it, so what a workflow receives - * is a fresh copy taken from that retained history rather than the retained history itself. + * is a fresh copy taken from that saved history rather than the saved history itself. */ export function consumable(result: Result): Result { if (result.status === "ok") { diff --git a/packages/durable-streams/specs/DECISIONS.md b/packages/durable-streams/specs/DECISIONS.md index cec63a894..b63bbaf56 100644 --- a/packages/durable-streams/specs/DECISIONS.md +++ b/packages/durable-streams/specs/DECISIONS.md @@ -140,7 +140,7 @@ Updated before completion of every phase and committed at the end of each phase. - **Phase:** 0 (Scaffolding) - **Date:** 2026-02-28 - **Context:** Spec §6.2-6.3 defines divergence at effect matching and at every - terminal boundary: return with retained effects, failure with retained + terminal boundary: return with saved effects, failure with saved effects, and continuation past a recorded close. - **Options considered:** 1. Single DivergenceError class with a `kind` field @@ -154,8 +154,8 @@ Updated before completion of every phase and committed at the end of each phase. terminal divergence carries consumed/total counts and may retain the active execution failure as its cause. The common terminal base keeps the existing early-return API compatible while covering exceptional termination honestly. -- **Consequences:** No root or child `Close` is appended while its retained - subtree is unaligned. Reaching a completed child claims its retained subtree; +- **Consequences:** No root or child `Close` is appended while its saved + subtree is unaligned. Reaching a completed child claims its saved subtree; abandoning that child is terminal divergence. Error handling uses `instanceof` rather than matching names. diff --git a/packages/durable-streams/specs/effection-integration.md b/packages/durable-streams/specs/effection-integration.md index 63186393d..7107d0db7 100644 --- a/packages/durable-streams/specs/effection-integration.md +++ b/packages/durable-streams/specs/effection-integration.md @@ -704,7 +704,7 @@ it returns/throws immediately. For `cancelled`, it uses `yield* suspend()` (see §8.4). **The same terminal policy as the root.** Normal return, ordinary error, and -cancellation all check the child's retained subtree before a `Close`. A stale +cancellation all check the child's saved subtree before a `Close`. A stale input, divergence, terminal divergence, or journal persistence failure suppresses the child close and propagates to the root without becoming a root close. @@ -923,7 +923,7 @@ Implemented in `run.ts`. Key implementation details beyond the original design: short-circuits on existing Close event (DEC-016), checks terminal replay alignment before either successful or exceptional closure, and emits `Close(err)` only for an ordinary workflow failure after the -whole retained coroutine tree is aligned. Durability and persistence failures +whole saved coroutine tree is aligned. Durability and persistence failures never become terminal events. The entry point creates a scope, builds the replay index, sets up the @@ -963,15 +963,15 @@ Key details: means no cast is needed at the `scope.run()` call site. - **Subtree alignment.** The root claims its own identity, and every durable - child claims its identity before using a retained `Close`. Before root - termination, the index rejects unconsumed yields and retained completed + child claims its identity before using a saved `Close`. Before root + termination, the index rejects unconsumed yields and saved completed children that the current definition never claimed. - **Exceptional terminal divergence check.** Before an ordinary error becomes `Close(err)`, checks the same subtree state. Unaligned entries produce `TerminalDivergenceError`, with the ordinary error as its cause, and leave the - retained stream unchanged. An already-active durability failure is preserved - even if it consumed the last retained yield. + saved stream unchanged. An already-active durability failure is preserved + even if it consumed the last saved yield. - **Backing persistence is outside workflow outcomes.** Every backing append failure records a shared `DurablePersistenceError` before it reaches root or @@ -1289,8 +1289,8 @@ All cases from §6.3 are implemented and tested (DEC-008): `ContinuePastCloseDivergenceError`. Tested in divergence test 14. 4. **Completed child is abandoned.** Each child identity is claimed before its - retained `Close` is restored. Root and child termination scan the whole - retained subtree, so a completed child absent from the current definition + saved `Close` is restored. Root and child termination scan the whole + saved subtree, so a completed child absent from the current definition raises terminal divergence and appends no `Close`. The same checks run at child termination. The terminal errors carry consumed diff --git a/packages/durable-streams/specs/protocol-specification.md b/packages/durable-streams/specs/protocol-specification.md index e28eb48ba..7ccf0d928 100644 --- a/packages/durable-streams/specs/protocol-specification.md +++ b/packages/durable-streams/specs/protocol-specification.md @@ -306,8 +306,8 @@ asked to produce what the event settled to — an eager read hands a backend tha cannot produce a result the ability to fail past every guard, carrying its own error. -Each retained Yield owns one cell for what it settled to, and **that cell spans -the whole replay lifecycle**: the check phase is handed the retained Yields +Each saved Yield owns one cell for what it settled to, and **that cell spans +the whole replay lifecycle**: the check phase is handed the saved Yields rather than the stream's own events, so a guard that validates a result and the replay path that later consumes it observe the same value. The stream's event is consulted at most once. Reading once across phases is the property that makes @@ -325,14 +325,14 @@ remembered and re-raised rather than retried, so a source cannot refuse the guard and then answer replay. **Every** event that participates in admission, indexing, or terminal reuse is -retained — Close as well as Yield. A Close decides whether a coroutine has a +saved — Close as well as Yield. A Close decides whether a coroutine has a terminal result to reuse, so leaving it as the backend's own object lets it belong to a child coroutine while one phase asks and to the root while the next does: the phase that admits the history sees no terminal result, and the phase that reuses one sees it. The discriminator is settled by the classification that chooses an event's -retained kind, and never read from the source again. Identity — the coroutine an +saved kind, and never read from the source again. Identity — the coroutine an event belongs to, and a Yield's complete effect description — is settled once too, so no phase can be shown a different event than the phase before it. An event that refuses to say what it is is refused from every member. @@ -340,7 +340,7 @@ event that refuses to say what it is is refused from every member. A Yield's settlement stays lazy, so a guard's check remains the first ordinary consumer of a recorded result. -A Close's result is settled while the history is retained. A Close carries what +A Close's result is settled while the history is saved. A Close carries what a completed run hands back, and deferring that read leaves an interval — between the moment a consumer's own admission accepts the history and the moment terminal reuse consumes it — in which the backend still owns the answer and can @@ -355,8 +355,8 @@ The detached result is ordinary mutable JSON. Detaching is the claim against the stream; making the copy immutable would be a claim against the consumer, and replayed values are legitimately written to. -After every retained event has been offered to `check` and before any recorded -terminal result is reused, guards receive the retained history once through +After every saved event has been offered to `check` and before any recorded +terminal result is reused, guards receive the saved history once through `admit`. A guard that requires something of the history as a whole — that an event it validates is present, and present once — refuses there, because a per-event check has nothing to object to in a journal that simply omits the @@ -365,19 +365,19 @@ event. Its default is a no-op. A replay distinguishes three views of one history, and conflating any two hands the decision to whoever holds the wrong one: -1. the **authoritative retained history**, which admission validated and replay +1. the **authoritative saved history**, which admission validated and replay consumes — detached from the backend and immutable to policy, its descriptions and results frozen through; 2. **isolated guard observations**, a deep mutable copy made per guard invocation, over which middleware composes freely and from which nothing flows back; and 3. **values delivered to workflow code**, a fresh mutable copy taken from the - retained history at consumption — for a replayed effect and for a completed run's + saved history at consumption — for a replayed effect and for a completed run's own return value alike. This is the only thing "mutable replayed value" - means: writing to that copy reaches neither the retained history nor the next + means: writing to that copy reaches neither the saved history nor the next replay. -The retained history is frozen through in every settlement shape: a success with a +The saved history is frozen through in every settlement shape: a success with a value, a `Result` with none, a failure, and a cancellation, on Yield and Close alike. An envelope left writable is one a public observation could add a value to before replay reads it. @@ -390,7 +390,7 @@ Replay guards are **composable policy, not enforcement**. Guards compose through middleware, and a handler installed further out may decline to delegate — which is what composition is for. An invariant that must not be negotiable therefore does not belong in a guard. A consumer whose durable identity depends on -validating retained history owns that validation itself, for example by wrapping +validating saved history owns that validation itself, for example by wrapping the `DurableStream` it passes to `durableRun` so the check happens inside the read every later phase depends on. @@ -405,7 +405,7 @@ class ReplayIndex { private closes = new Map(); constructor(events: DurableEvent[]) { - // Retained first, idempotently: a caller that already produced the stable + // Saved first, idempotently: a caller that already produced the stable // history hands the same objects on rather than a second wrapping of them. for (const event of retainEvents(events)) { if (event.type === "yield") { @@ -688,11 +688,11 @@ run — effects were removed without a version gate. **Generator fails early.** The generator throws while the replay index still has unconsumed entries. The ordinary execution error does not describe a -completed replay of the retained history. The runtime raises +completed replay of the saved history. The runtime raises `TerminalDivergenceError`, retaining the execution error as its cause, and appends neither that ordinary error nor any root `Close`. -**Retained child is not claimed.** A retained child coroutine has completed, +**Saved child is not claimed.** A saved child coroutine has completed, but the current definition terminates its parent without reaching that child identity. Its `Close` proves only that the earlier child completed; it does not make the current coroutine tree aligned. @@ -703,17 +703,17 @@ finished after consuming all recorded yields. This means the current code produces more effects than the recorded run — effects were added without a version gate. -Every terminal divergence diagnostic names the first retained protocol +Every terminal divergence diagnostic names the first saved protocol entry the terminating subtree did not reach, beside the consumed and total counts and the ordinary failure cause, which remain unchanged. An unreached `Yield` is rendered as its `type("name")` with its optional authored source, exactly as §6.2 renders a mismatch side. When the first -unreached entry is a `Close` — a removed completed child that retained +unreached entry is a `Close` — a removed completed child that saved no `Yield` — the diagnostic names that `Close` and its coroutine rather than inventing an effect or a source. Terminal alignment covers the whole terminating coroutine subtree. All -terminal divergence cases raise a durability error and leave the retained +terminal divergence cases raise a durability error and leave the saved journal unchanged. A durability failure discovered after the last replay entry was consumed remains a durability failure and is never serialized as `Close(err)`. Once any durability failure is active, no later replay entry is @@ -882,7 +882,7 @@ Version gates follow a three-phase lifecycle: 1. **Add gate.** Deploy code with both old (v0) and new (v1) paths. In-flight workflows replay with v0. New workflows execute with v1. 2. **Deprecate old path.** Once all v0 workflows have completed, the - v0 code path is dead but retained for safety. + v0 code path is dead but saved for safety. 3. **Remove gate.** Once all v1 workflows from the deprecation era have completed, remove the gate and the old code path entirely. @@ -1018,9 +1018,9 @@ restored as a value `JSON.stringify` cannot write back. `MalformedDurableEventError.path` locates the offending member, such as `$.result.error.message`. Members the protocol does not name — the extra description fields, anything inside a result value — appear as `*`, because a -record's own member names are as much retained content as its values. Neither +record's own member names are as much saved content as its values. Neither the path nor the message repeats anything from the record: a journal is -retained, filtered history, and a parse failure is not a reason to copy its +saved, filtered history, and a parse failure is not a reason to copy its contents into an error that travels to logs and terminals. ### 11.3 Stream consistency @@ -1202,10 +1202,10 @@ These tests MUST pass for the protocol to be considered implemented. | 11 | **Type mismatch** | Record a `call` effect. Replay code yields `sleep` at same position. | `DivergenceError` citing type mismatch. | | 12 | **Name mismatch** | Record `call("fetchOrder")`. Replay yields `call("chargeCard")`. | `DivergenceError` citing name mismatch. | | 12b | **Positioned mismatch** | Record a description carrying `"executablemd.source-position"`. Replay yields a differently named effect carrying its own position. | `DivergenceError` names both authored sites, one per side; identical extra metadata still matches. | -| 13 | **Generator finishes early** | Record a partial stream with 5 yields and no root close. Replay code produces only 3 yields then returns. | `EarlyReturnDivergenceError` naming the first unreached retained `Yield`; no root `Close` is appended. | -| 13b | **Generator fails early** | Record a partial stream with a retained yield and no root close. Replay code throws before reaching it. | `TerminalDivergenceError` naming the first unreached retained `Yield`; the original error is its cause and no root `Close` is appended. | -| 13c | **Completed child removed** | Record a child `Yield` and `Close`, omit the root `Close`, then remove the child from the current definition. | Root return and root failure both reject the unchanged prefix, naming the child's first retained `Yield` — or its `Close` when it retained none; the compatible definition still replays it. | -| 13d | **Child cancellation finalization** | Record a child with retained history, then cancel that child before its history is exhausted. | The cancellation `TerminalDivergenceError` names the first unreached retained entry like every other terminal path. | +| 13 | **Generator finishes early** | Record a partial stream with 5 yields and no root close. Replay code produces only 3 yields then returns. | `EarlyReturnDivergenceError` naming the first unreached saved `Yield`; no root `Close` is appended. | +| 13b | **Generator fails early** | Record a partial stream with a saved yield and no root close. Replay code throws before reaching it. | `TerminalDivergenceError` naming the first unreached saved `Yield`; the original error is its cause and no root `Close` is appended. | +| 13c | **Completed child removed** | Record a child `Yield` and `Close`, omit the root `Close`, then remove the child from the current definition. | Root return and root failure both reject the unchanged prefix, naming the child's first saved `Yield` — or its `Close` when it saved none; the compatible definition still replays it. | +| 13d | **Child cancellation finalization** | Record a child with saved history, then cancel that child before its history is exhausted. | The cancellation `TerminalDivergenceError` names the first unreached saved entry like every other terminal path. | | 14 | **Generator continues past close** | Record stream with close after 3 yields. Replay code produces 5 yields. | `DivergenceError`: journal shows close but generator hasn't finished. | ### Tier 3 — Structured concurrency @@ -1221,7 +1221,7 @@ These tests MUST pass for the protocol to be considered implemented. | 21 | **Error boundary** | Child error caught by parent `try/catch`. | Parent catches error; siblings NOT cancelled; execution continues. | | 22 | **Race — winner cancels losers** | `race([op1, op2])`, op1 wins. | op2 receives `Close(cancelled)`; race returns op1's result. | | 23 | **Race replay with partial loser** | Replay race where loser partially executed. | Loser's partial yields replayed, then cancelled at correct point. | -| 23b | **Child durability failure** | Make replay stale or divergent inside a retained child. | Neither the child nor root appends `Close`; the exact durability failure escapes and the prefix remains unchanged. | +| 23b | **Child durability failure** | Make replay stale or divergent inside a saved child. | Neither the child nor root appends `Close`; the exact durability failure escapes and the prefix remains unchanged. | ### Tier 4 — Deterministic identity diff --git a/packages/durable-streams/tests/deterministic-id.test.ts b/packages/durable-streams/tests/deterministic-id.test.ts index 94a9106da..4fbd140aa 100644 --- a/packages/durable-streams/tests/deterministic-id.test.ts +++ b/packages/durable-streams/tests/deterministic-id.test.ts @@ -206,7 +206,7 @@ describe("deterministic IDs", () => { const partialStream = new InMemoryStream(partialEvents); - // The new third child has no retained entries, so it executes live. + // The new third child has no saved entries, so it executes live. const tracker2 = createCallTracker(); // With 3 children, root's childCounter goes to 3, but journal has diff --git a/packages/durable-streams/tests/divergence.test.ts b/packages/durable-streams/tests/divergence.test.ts index 3ee00827f..0a084dc51 100644 --- a/packages/durable-streams/tests/divergence.test.ts +++ b/packages/durable-streams/tests/divergence.test.ts @@ -493,7 +493,7 @@ describe("divergence detection", () => { expect(executions).toBe(0); }); - it("early return names the first unreached retained yield", function* () { + it("early return names the first unreached saved yield", function* () { const events: DurableEvent[] = [ { type: "yield", @@ -535,7 +535,7 @@ describe("divergence detection", () => { expect(stream.snapshot()).toEqual(before); }); - it("ordinary failure names the first unreached retained yield and keeps its cause", function* () { + it("ordinary failure names the first unreached saved yield and keeps its cause", function* () { const original = new Error("current workflow failed"); const events: DurableEvent[] = [ { @@ -574,7 +574,7 @@ describe("divergence detection", () => { expect(stream.snapshot()).toEqual(events); }); - it("a malformed retained source keeps the existing diagnostic wording", function* () { + it("a malformed saved source keeps the existing diagnostic wording", function* () { const events: DurableEvent[] = [ { type: "yield", @@ -684,7 +684,7 @@ describe("divergence detection", () => { } }); - it("a terminal diagnostic omits a retained source missing its offset", function* () { + it("a terminal diagnostic omits a saved source missing its offset", function* () { const events: DurableEvent[] = [ { type: "yield", @@ -724,7 +724,7 @@ describe("divergence detection", () => { } }); - it("a terminal diagnostic omits a retained source with an extra member", function* () { + it("a terminal diagnostic omits a saved source with an extra member", function* () { const original = new Error("current workflow failed"); const events: DurableEvent[] = [ { diff --git a/packages/durable-streams/tests/durable-position.test.ts b/packages/durable-streams/tests/durable-position.test.ts index 8de5f8080..56c0bfbd9 100644 --- a/packages/durable-streams/tests/durable-position.test.ts +++ b/packages/durable-streams/tests/durable-position.test.ts @@ -4,7 +4,7 @@ * A position exists so that an operation with no name of its own can still be * the same operation next time. Everything asserted here is that sameness: the * index a coroutine reaches at one point in a procedure is the index it reaches - * there on replay, on a live suffix continuing past retained history, and + * there on replay, on a live suffix continuing past saved history, and * inside a child that counts its own. * * The discriminating observation is a *live suffix after a replayed prefix*. A @@ -82,16 +82,16 @@ describe("Tier DP — durable coroutine position", () => { })(), ); - // The same procedure against retained history that stops part-way: the + // The same procedure against saved history that stops part-way: the // first two effects replay and the third runs live. This is the case a // position taken from replay's cursor gets wrong — the cursor stops - // advancing where retained history ends, so the live suffix would repeat + // advancing where saved history ends, so the live suffix would repeat // an index the prefix already used. - const retained: DurableEvent[] = (yield* first.readAll()).filter( + const saved: DurableEvent[] = (yield* first.readAll()).filter( (event) => event.type !== "close", ); const partial = new InMemoryStream( - retained.filter((event) => { + saved.filter((event) => { const description = Reflect.get(Object(event), "description"); return Reflect.get(Object(description), "name") !== "third"; }), diff --git a/packages/durable-streams/tests/fail-stop.test.ts b/packages/durable-streams/tests/fail-stop.test.ts index 64af4329b..f0c29001b 100644 --- a/packages/durable-streams/tests/fail-stop.test.ts +++ b/packages/durable-streams/tests/fail-stop.test.ts @@ -312,7 +312,7 @@ describe("durable fail-stop boundary", () => { }); it("fences replay consumption and live execution after a caught divergence", function* () { - const retained: DurableEvent[] = [ + const saved: DurableEvent[] = [ { type: "yield", coroutineId: "root", @@ -326,7 +326,7 @@ describe("durable fail-stop boundary", () => { result: { status: "ok", value: "two" }, }, ]; - const stream = new InMemoryStream(retained); + const stream = new InMemoryStream(saved); const before = eventBytes(stream.snapshot()); let replayDecisions = 0; let laterExecutions = 0; diff --git a/packages/durable-streams/tests/guard-stream.test.ts b/packages/durable-streams/tests/guard-stream.test.ts index 4c91c9b41..65e42f0a0 100644 --- a/packages/durable-streams/tests/guard-stream.test.ts +++ b/packages/durable-streams/tests/guard-stream.test.ts @@ -133,7 +133,7 @@ describe("journal provenance", () => { } expect(duplicate).toBeInstanceOf(Error); // A refused duplicate leaves the original witness in place rather than - // minting a second one the provider never retained. + // minting a second one the provider never saved. expect(getJournalProvenance(backend)).toBe(provenance); }); diff --git a/packages/durable-streams/tests/replay-guard.test.ts b/packages/durable-streams/tests/replay-guard.test.ts index 1867d84b1..9195d8942 100644 --- a/packages/durable-streams/tests/replay-guard.test.ts +++ b/packages/durable-streams/tests/replay-guard.test.ts @@ -649,15 +649,15 @@ describe("replay guard", () => { * * The unit rows above prove repeated access through one `YieldEntry` is cached. * They cannot prove the property that matters, which spans two phases: a guard - * validates a retained result in the check phase, and the replay path consumes + * validates a saved result in the check phase, and the replay path consumes * it afterwards. If those are two reads of the stream's own event, a source * answering differently between them has the guard approve one value while * execution uses another, and nothing downstream can detect it. * - * The journal here is partial — no Close — so replay consumes the retained + * The journal here is partial — no Close — so replay consumes the saved * Yield and then continues live, which is the shape that reaches both phases. */ -describe("durableRun — one retained result across check and replay", () => { +describe("durableRun — one saved result across check and replay", () => { it("hands the check phase and replay the same first answer", function* () { const answers: Json[] = ["first", "second"]; const reads = { count: 0 }; @@ -724,14 +724,14 @@ describe("durableRun — one retained result across check and replay", () => { * validates one tree and replay consumes another, with nothing in between able * to notice. */ -/** One member of a retained JSON object, when it really is one. */ +/** One member of a saved JSON object, when it really is one. */ function member(value: Json | undefined, key: string): Json | undefined { return value !== null && typeof value === "object" && !Array.isArray(value) ? value[key] : undefined; } -describe("durableRun — a retained result detaches completely", () => { +describe("durableRun — a saved result detaches completely", () => { it("a nested accessor is read once and cannot answer twice", function* () { const answers = ["first", "second"]; const reads = { count: 0 }; @@ -858,11 +858,11 @@ describe("durableRun — a retained result detaches completely", () => { * * A replay guard is composable policy. Composition means reading, annotating, * and passing along; it must not mean editing the history the execution already - * validated. Given the retained events themselves, a guard could rename effect + * validated. Given the saved events themselves, a guard could rename effect * A to B and have a workflow asking for B consume A's result, without B ever * running. */ -describe("replay guard — observation is isolated from the retained history", () => { +describe("replay guard — observation is isolated from the saved history", () => { function journal(): DurableEvent[] { return [ { @@ -967,7 +967,7 @@ describe("replay guard — observation is isolated from the retained history", ( const entry = index.peekYield("root"); expect(entry).toBeDefined(); - // Whatever a caller does with what the index hands back, the retained + // Whatever a caller does with what the index hands back, the saved // answer is unchanged. try { Object.assign(entry!.description, { name: "B" }); @@ -996,15 +996,15 @@ describe("replay guard — observation is isolated from the retained history", ( }); /** - * Every Result shape is retained history; every consumer value is a copy. + * Every Result shape is saved history; every consumer value is a copy. * * The distinction has to hold for all four settlements, not only the one that * carries data. A `Result` left writable is an envelope a public * observation could add a value to before replay reads it, and a completed run - * that hands back the retained value itself gives a caller a frozen object + * that hands back the saved value itself gives a caller a frozen object * where the live run gave ordinary JSON. */ -describe("retained history — every settlement, and its consumer copy", () => { +describe("saved history — every settlement, and its consumer copy", () => { function completed(value: Json): DurableEvent[] { return [{ type: "close", coroutineId: "root", result: { status: "ok", value } }]; } @@ -1062,7 +1062,7 @@ describe("retained history — every settlement, and its consumer copy", () => { expect(second).toEqual({ count: 1 }); }); - it("a retained ok-without-value settlement is frozen through", function* () { + it("a saved ok-without-value settlement is frozen through", function* () { const index = new ReplayIndex( retainEvents([ { @@ -1083,7 +1083,7 @@ describe("retained history — every settlement, and its consumer copy", () => { expect(index.peekYield("root")?.result).toEqual({ status: "ok" }); }); - it("a retained void Close settlement is frozen through", function* () { + it("a saved void Close settlement is frozen through", function* () { const index = new ReplayIndex( retainEvents([{ type: "close", coroutineId: "root", result: { status: "ok" } }]), ); diff --git a/packages/durable-streams/tests/retained.test.ts b/packages/durable-streams/tests/saved.test.ts similarity index 80% rename from packages/durable-streams/tests/retained.test.ts rename to packages/durable-streams/tests/saved.test.ts index 72f6251c6..aad69d827 100644 --- a/packages/durable-streams/tests/retained.test.ts +++ b/packages/durable-streams/tests/saved.test.ts @@ -1,5 +1,5 @@ /** - * Retained history — one settled answer per event, for every phase. + * Saved history — one settled answer per event, for every phase. * * A journal is data a backend supplies, and every phase of a replay reads the * same events: a consumer's own admission gate, the replay index, public guard @@ -15,7 +15,7 @@ import { describe, it } from "@executablemd/test-support/bdd"; import { expect } from "@executablemd/test-support/expect"; -import { detachJson, retainEvents } from "../retained.ts"; +import { detachJson, retainEvents } from "../saved.ts"; import { ReplayIndex } from "../replay-index.ts"; import type { DurableEvent, Json } from "../types.ts"; @@ -44,7 +44,7 @@ function retain(event: DurableEvent): DurableEvent { return retainEvents([event])[0]!; } -describe("retained history — classification settles once", () => { +describe("saved history — classification settles once", () => { it("an event that turns from Yield into Close stays a Yield", function* () { const { event, reads } = shifting( { type: ["yield", "close"] }, @@ -54,9 +54,9 @@ describe("retained history — classification settles once", () => { result: { status: "ok" }, }, ); - const retained = retain(event); - expect(retained.type).toBe("yield"); - expect(retained.type).toBe("yield"); + const saved = retain(event); + expect(saved.type).toBe("yield"); + expect(saved.type).toBe("yield"); // Classified once; the wrapper never asks the source again. expect(reads["type"]).toBe(1); }); @@ -66,9 +66,9 @@ describe("retained history — classification settles once", () => { { type: ["close", "yield"] }, { coroutineId: "root", result: { status: "ok", value: "kept" } }, ); - const retained = retain(event); - expect(retained.type).toBe("close"); - expect(retained.type).toBe("close"); + const saved = retain(event); + expect(saved.type).toBe("close"); + expect(saved.type).toBe("close"); expect(reads["type"]).toBe(1); }); @@ -82,8 +82,8 @@ describe("retained history — classification settles once", () => { throw new Error("the backend will not say what this event is"); }, }); - const retained = retain(event as unknown as DurableEvent); - for (const read of [() => retained.type, () => retained.coroutineId, () => retained.result]) { + const saved = retain(event as unknown as DurableEvent); + for (const read of [() => saved.type, () => saved.coroutineId, () => saved.result]) { let caught: unknown; try { read(); @@ -98,17 +98,17 @@ describe("retained history — classification settles once", () => { }); it("an event that is neither is refused rather than passed through", function* () { - const retained = retain({ type: "something-else" } as unknown as DurableEvent); + const saved = retain({ type: "something-else" } as unknown as DurableEvent); let caught: unknown; try { - void retained.type; + void saved.type; } catch (error) { caught = error; } expect(caught).toBeInstanceOf(TypeError); }); - it("retaining an already-retained event returns it unchanged", function* () { + it("retaining an already-saved event returns it unchanged", function* () { const once = retain({ type: "yield", coroutineId: "root", @@ -119,20 +119,20 @@ describe("retained history — classification settles once", () => { }); }); -describe("retained history — a Close settles once", () => { +describe("saved history — a Close settles once", () => { it("a coroutine that moves from a child to the root stays the child's", function* () { const { event, reads } = shifting( { coroutineId: ["root.7", "root"] }, { type: "close", result: { status: "ok", value: "alpha" } }, ); - const retained = retain(event); - expect(retained.coroutineId).toBe("root.7"); - expect(retained.coroutineId).toBe("root.7"); + const saved = retain(event); + expect(saved.coroutineId).toBe("root.7"); + expect(saved.coroutineId).toBe("root.7"); expect(reads["coroutineId"]).toBe(1); // And the index — the phase that decides whether a terminal result exists — // sees exactly what the first reader saw. - const index = new ReplayIndex([retained]); + const index = new ReplayIndex([saved]); expect(index.hasClose("root")).toBe(false); expect(index.hasClose("root.7")).toBe(true); }); @@ -157,9 +157,9 @@ describe("retained history — a Close settles once", () => { }, { type: "close", coroutineId: "root" }, ); - const retained = retain(event); - expect(retained.result).toEqual({ status: "ok", value: "first" }); - expect(retained.result).toEqual({ status: "ok", value: "first" }); + const saved = retain(event); + expect(saved.result).toEqual({ status: "ok", value: "first" }); + expect(saved.result).toEqual({ status: "ok", value: "first" }); expect(reads["result"]).toBe(1); }); @@ -173,9 +173,9 @@ describe("retained history — a Close settles once", () => { }, { type: "close", coroutineId: "root" }, ); - const retained = retain(event); - expect(retained.result).toEqual({ status: "err", error: { message: "first" } }); - expect(retained.result).toEqual({ status: "err", error: { message: "first" } }); + const saved = retain(event); + expect(saved.result).toEqual({ status: "err", error: { message: "first" } }); + expect(saved.result).toEqual({ status: "err", error: { message: "first" } }); }); it("a terminal result that refuses and then answers stays refused", function* () { @@ -191,11 +191,11 @@ describe("retained history — a Close settles once", () => { return { status: "ok", value: "answered later" }; }, }); - const retained = retain(event as unknown as DurableEvent); + const saved = retain(event as unknown as DurableEvent); for (let attempt = 0; attempt < 3; attempt++) { let caught: unknown; try { - void retained.result; + void saved.result; } catch (error) { caught = error; } @@ -207,7 +207,7 @@ describe("retained history — a Close settles once", () => { }); /** - * Detached while the history is retained, not at a first later read. + * Detached while the history is saved, not at a first later read. * * Memoizing on first access closes repeated reads and leaves the interval * between a consumer's admission and terminal reuse open: nobody has touched @@ -215,36 +215,36 @@ describe("retained history — a Close settles once", () => { */ it("a terminal result is detached before anyone reads it", function* () { const value: Record = { output: "original" }; - const retained = retain({ + const saved = retain({ type: "close", coroutineId: "root", result: { status: "ok", value }, } as unknown as DurableEvent); - // Mutated before the retained result has ever been read. + // Mutated before the saved result has ever been read. value["output"] = "planted after retention"; - expect(retained.result).toEqual({ status: "ok", value: { output: "original" } }); + expect(saved.result).toEqual({ status: "ok", value: { output: "original" } }); }); it("a terminal result is detached from the source", function* () { const value = { list: ["a"] }; - const retained = retain({ + const saved = retain({ type: "close", coroutineId: "root", result: { status: "ok", value }, } as unknown as DurableEvent); - const held = retained.result; + const held = saved.result; value.list.push("injected"); expect(held).toEqual({ status: "ok", value: { list: ["a"] } }); }); - it("a cancelled terminal result is retained as itself", function* () { - const retained = retain({ + it("a cancelled terminal result is saved as itself", function* () { + const saved = retain({ type: "close", coroutineId: "root", result: { status: "cancelled" }, } as unknown as DurableEvent); - expect(retained.result).toEqual({ status: "cancelled" }); + expect(saved.result).toEqual({ status: "cancelled" }); }); it("the control — an intact history classifies and settles normally", function* () { @@ -267,7 +267,7 @@ describe("retained history — a Close settles once", () => { }); }); -describe("retained history — a Yield's identity settles once", () => { +describe("saved history — a Yield's identity settles once", () => { const BASE = { result: { status: "ok", value: 1 } }; it("a name that turns into __root__ is seen the same way by every phase", function* () { @@ -280,14 +280,14 @@ describe("retained history — a Yield's identity settles once", () => { enumerable: true, get: Object.getOwnPropertyDescriptor(event, "name")!.get!, }); - const retained = retain({ + const saved = retain({ type: "yield", coroutineId: "root", description, ...BASE, } as unknown as DurableEvent); - expect(retained.type === "yield" ? retained.description.name : undefined).toBe("unrelated"); - expect(retained.type === "yield" ? retained.description.name : undefined).toBe("unrelated"); + expect(saved.type === "yield" ? saved.description.name : undefined).toBe("unrelated"); + expect(saved.type === "yield" ? saved.description.name : undefined).toBe("unrelated"); }); it("a name that turns away from __root__ keeps naming the root import", function* () { @@ -300,14 +300,14 @@ describe("retained history — a Yield's identity settles once", () => { enumerable: true, get: Object.getOwnPropertyDescriptor(event, "name")!.get!, }); - const retained = retain({ + const saved = retain({ type: "yield", coroutineId: "root", description, ...BASE, } as unknown as DurableEvent); - expect(retained.type === "yield" ? retained.description.name : undefined).toBe("__root__"); - expect(retained.type === "yield" ? retained.description.name : undefined).toBe("__root__"); + expect(saved.type === "yield" ? saved.description.name : undefined).toBe("__root__"); + expect(saved.type === "yield" ? saved.description.name : undefined).toBe("__root__"); }); it("a shifting coroutine id settles once", function* () { @@ -315,9 +315,9 @@ describe("retained history — a Yield's identity settles once", () => { { coroutineId: ["root", "root.3"] }, { type: "yield", description: { type: "call", name: "work" }, ...BASE }, ); - const retained = retain(event); - expect(retained.coroutineId).toBe("root"); - expect(retained.coroutineId).toBe("root"); + const saved = retain(event); + expect(saved.coroutineId).toBe("root"); + expect(saved.coroutineId).toBe("root"); expect(reads["coroutineId"]).toBe(1); }); @@ -331,13 +331,13 @@ describe("retained history — a Yield's identity settles once", () => { return asked === 1 ? "stable" : "swapped"; }, }); - const retained = retain({ + const saved = retain({ type: "yield", coroutineId: "root", description, ...BASE, } as unknown as DurableEvent); - const read = () => (retained.type === "yield" ? retained.description["marker"] : undefined); + const read = () => (saved.type === "yield" ? saved.description["marker"] : undefined); expect(read()).toBe("stable"); expect(read()).toBe("stable"); expect(asked).toBe(1); @@ -357,11 +357,11 @@ describe("retained history — a Yield's identity settles once", () => { throw new Error("the backend will not say which coroutine this is"); }, }); - const retained = retain(event as unknown as DurableEvent); + const saved = retain(event as unknown as DurableEvent); for (let attempt = 0; attempt < 3; attempt++) { let caught: unknown; try { - void retained.coroutineId; + void saved.coroutineId; } catch (error) { caught = error; } @@ -395,8 +395,8 @@ describe("retained history — a Yield's identity settles once", () => { }); }); -describe("retained history — detached values stay ordinary JSON", () => { - it("`__proto__` is retained as an own data member", function* () { +describe("saved history — detached values stay ordinary JSON", () => { + it("`__proto__` is saved as an own data member", function* () { const source: Record = {}; Object.defineProperty(source, "__proto__", { value: { polluted: true }, diff --git a/packages/durable-streams/tests/terminal-boundary.test.ts b/packages/durable-streams/tests/terminal-boundary.test.ts index 96d02b43b..e7b393e45 100644 --- a/packages/durable-streams/tests/terminal-boundary.test.ts +++ b/packages/durable-streams/tests/terminal-boundary.test.ts @@ -76,7 +76,7 @@ function* recordTwoStepChild(): Operation { } describe("durable terminal boundary", () => { - it("claiming a completed child aligns its retained descendants", function* () { + it("claiming a completed child aligns its saved descendants", function* () { const golden = new InMemoryStream(); let executions = 0; const nestedWorkflow = function* (): Workflow { @@ -97,8 +97,8 @@ describe("durable terminal boundary", () => { }; yield* durableRun(nestedWorkflow, { stream: golden }); - const retained = golden.snapshot().filter((event) => event.coroutineId !== "root"); - const replay = new InMemoryStream(retained); + const saved = golden.snapshot().filter((event) => event.coroutineId !== "root"); + const replay = new InMemoryStream(saved); expect(yield* durableRun(nestedWorkflow, { stream: replay })).toBe("recorded"); expect(executions).toBe(1); @@ -106,8 +106,8 @@ describe("durable terminal boundary", () => { }); it("rejects a normal root return that abandons a completed child", function* () { - const retained = yield* recordCompletedChild(); - const stream = new InMemoryStream(retained); + const saved = yield* recordCompletedChild(); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); let failure: unknown; @@ -126,14 +126,14 @@ describe("durable terminal boundary", () => { expect(failure).toBeInstanceOf(EarlyReturnDivergenceError); expectUnchanged(stream, before); - const compatible = new InMemoryStream(retained); + const compatible = new InMemoryStream(saved); expect(yield* replayCompletedChild(compatible)).toBe("recorded"); expect(compatible.snapshot().at(-1)?.coroutineId).toBe("root"); }); it("rejects a root error that abandons a completed child", function* () { - const retained = yield* recordCompletedChild(); - const stream = new InMemoryStream(retained); + const saved = yield* recordCompletedChild(); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); const workflowFailure = new Error("current root failed"); let failure: unknown; @@ -160,8 +160,8 @@ describe("durable terminal boundary", () => { it("does not close a child or root over stale child history", function* () { const completed = yield* recordCompletedChild(); - const retained = completed.filter((event) => event.type === "yield"); - const stream = new InMemoryStream(retained); + const saved = completed.filter((event) => event.type === "yield"); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); const stale = new StaleInputError("child input changed"); @@ -187,8 +187,8 @@ describe("durable terminal boundary", () => { it("does not close a child or root over divergent child history", function* () { const completed = yield* recordCompletedChild(); - const retained = completed.filter((event) => event.type === "yield"); - const stream = new InMemoryStream(retained); + const saved = completed.filter((event) => event.type === "yield"); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); let failure: unknown; @@ -215,8 +215,8 @@ describe("durable terminal boundary", () => { }); it("checks child alignment before a successful child Close", function* () { - const retained = yield* recordTwoStepChild(); - const stream = new InMemoryStream(retained); + const saved = yield* recordTwoStepChild(); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); let failure: unknown; @@ -242,8 +242,8 @@ describe("durable terminal boundary", () => { }); it("checks child alignment before a failed child Close", function* () { - const retained = yield* recordTwoStepChild(); - const stream = new InMemoryStream(retained); + const saved = yield* recordTwoStepChild(); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); const childFailure = new Error("child failed early"); let failure: unknown; @@ -273,9 +273,9 @@ describe("durable terminal boundary", () => { expectUnchanged(stream, before); }); - it("names the first unreached retained yield when a completed child is removed", function* () { - const retained = yield* recordCompletedChild(); - const stream = new InMemoryStream(retained); + it("names the first unreached saved yield when a completed child is removed", function* () { + const saved = yield* recordCompletedChild(); + const stream = new InMemoryStream(saved); let failure: unknown; try { @@ -297,7 +297,7 @@ describe("durable terminal boundary", () => { expect(failure.message).toContain('first unreached entry is call("child-step")'); }); - it("a removed completed child with no yields names its retained Close", function* () { + it("a removed completed child with no yields names its saved Close", function* () { const golden = new InMemoryStream(); yield* durableRun( function* (): Workflow { @@ -311,8 +311,8 @@ describe("durable terminal boundary", () => { }, { stream: golden }, ); - const retained = golden.snapshot().filter((event) => event.coroutineId !== "root"); - const stream = new InMemoryStream(retained); + const saved = golden.snapshot().filter((event) => event.coroutineId !== "root"); + const stream = new InMemoryStream(saved); const before = stream.snapshot(); let failure: unknown; @@ -338,7 +338,7 @@ describe("durable terminal boundary", () => { expectUnchanged(stream, before); }); - it("child cancellation finalization names the first unreached retained entry", function* () { + it("child cancellation finalization names the first unreached saved entry", function* () { const events: DurableEvent[] = [ { type: "yield", @@ -387,11 +387,11 @@ describe("durable terminal boundary", () => { if (!(failure instanceof TerminalDivergenceError)) { throw new Error("expected terminal divergence"); } - expect(failure.message).toContain("cancelled before retained history was exhausted"); + expect(failure.message).toContain("cancelled before saved history was exhausted"); expect(failure.message).toContain( 'first unreached entry is call("child-second") at docs/Plan.md:12:1', ); - // The retained prefix is unchanged: no Close was appended over the + // The saved prefix is unchanged: no Close was appended over the // unconsumed history of the cancelled child, and none for the root. expect( stream diff --git a/packages/git/deno.ts b/packages/git/deno.ts index 2c3bfcd86..7eb22fc98 100644 --- a/packages/git/deno.ts +++ b/packages/git/deno.ts @@ -48,5 +48,5 @@ export { gitWorkspaceAttachment } from "./src/deno/attachment.ts"; export type { GitWorkspaceOptions } from "./src/deno/attachment.ts"; export { useRunComposition } from "./src/deno/run-composition/provider.ts"; export type { RunCompositionOptions } from "./src/deno/run-composition/provider.ts"; -export { useReplRepositoryRecording } from "./src/deno/retained-repository/provider.ts"; -export type { ReplRecordingOptions } from "./src/deno/retained-repository/provider.ts"; +export { useReplRepositoryRecording } from "./src/deno/saved-repository/provider.ts"; +export type { ReplRecordingOptions } from "./src/deno/saved-repository/provider.ts"; diff --git a/packages/git/mod.ts b/packages/git/mod.ts index 88c3390ac..96e75d7ab 100644 --- a/packages/git/mod.ts +++ b/packages/git/mod.ts @@ -27,7 +27,7 @@ * * ``` * - * A `` is retained in the run's Workspace: its bytes, the row that + * A `` is saved in the run's Workspace: its bytes, the row that * names it, and the Workspace root they were published against all commit * together or not at all. So a resumed run continues from the checkout it * recorded rather than from whatever is on the machine now, and a replay @@ -58,7 +58,7 @@ * * These components were published by `@executablemd/workflow` before this * package existed, and every journal, database row and artifact a released - * build wrote names that origin. Those strings identify retained history rather + * build wrote names that origin. Those strings identify saved history rather * than current source ownership, so the `@executablemd/workflow/composition` * origin is preserved exactly here. What a generated `` runs as is not * among them: that is stated by core's own declaration, which owns the diff --git a/packages/git/package.json b/packages/git/package.json index cd961e800..79a4287e3 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -1,7 +1,7 @@ { "name": "@executablemd/git", "version": "0.13.1", - "description": "The Git Plugin for executable.md: repositories, worktrees, Git operations, pull requests and issues, retained in a workflow run's Workspace.", + "description": "The Git Plugin for executable.md: repositories, worktrees, Git operations, pull requests and issues, saved in a workflow run's Workspace.", "type": "module", "exports": { ".": "./mod.ts", diff --git a/packages/git/src/composition/api.ts b/packages/git/src/composition/api.ts index a87686a58..8d4a5791d 100644 --- a/packages/git/src/composition/api.ts +++ b/packages/git/src/composition/api.ts @@ -6,7 +6,7 @@ * filesystem and no runtime: whichever host has the permission to touch a Git * checkout installs a concrete provider, and there are two of them. A workflow * host installs one inside its `withWorkflowWorkspace()` attachment, where a - * checkout is a retained Workspace root; the Deno and compiled `xmd run` + * checkout is a saved Workspace root; the Deno and compiled `xmd run` * entrypoints install another, where a checkout is a directory on the caller's * own filesystem held open by an advisory lock. * @@ -64,7 +64,7 @@ export interface RepositoryApi { * has one question: which repository am I acting on. A workflow provider * still performs both halves inside it — one durable effect that clones, * resolves, pins and retains, then an ephemeral reattachment that proves the - * retained state is still there — and a live provider acquires a lease, + * saved state is still there — and a live provider acquires a lease, * revalidates a compatible reuse and hands back the same directory. */ select(request: RepositoryRequest): Operation; diff --git a/packages/git/src/composition/components/GitCommit.ts b/packages/git/src/composition/components/GitCommit.ts index 6fc44a44d..f51d51d37 100644 --- a/packages/git/src/composition/components/GitCommit.ts +++ b/packages/git/src/composition/components/GitCommit.ts @@ -9,7 +9,7 @@ * The message prop, rendered content or both supply the message; both place the * prop first and content one blank line later. Canonicalization changes line * endings to LF, removes trailing whitespace and adds one final newline, preserving - * leading and interior text. Git receives those bytes verbatim; the retained digest + * leading and interior text. Git receives those bytes verbatim; the saved digest * and byte length describe them. The component renders nothing and requires `as`. */ @@ -118,7 +118,7 @@ function composed(text: string, source: GitCommitMessageSource): ComposedCommitM * * Applied wherever a request enters rather than only where a document writes * one: the Api is public, and a caller reaching it directly commits the same - * bytes under the same retained digest. + * bytes under the same saved digest. * * An unpaired surrogate is refused rather than replaced. The way to Git is * UTF-8, and encoding one produces U+FFFD, so what was committed would differ diff --git a/packages/git/src/composition/components/GitSwitch.ts b/packages/git/src/composition/components/GitSwitch.ts index 2c34e6519..f52456f3d 100644 --- a/packages/git/src/composition/components/GitSwitch.ts +++ b/packages/git/src/composition/components/GitSwitch.ts @@ -4,12 +4,12 @@ * * `` uses the enclosing Repository * and contextual cwd; a Dir may select one of its linked worktrees. Both values - * are observations the provider verifies against retained state, never permission. + * are observations the provider verifies against saved state, never permission. * The component renders and binds nothing; before/after evidence is journal-only. * * Git refusals such as an unresolved base, an occupied branch or overwritten local * changes become fixed GitOperationError failures recoverable by PrintErrors. - * Missing or unretained Repository selection, cwd outside its checkouts and identity + * Missing or unsaved Repository selection, cwd outside its checkouts and identity * mismatch are GitOperationAdmissionError failures: printing cannot recover them, * and they publish no outcome. */ diff --git a/packages/git/src/composition/components/PullRequest.ts b/packages/git/src/composition/components/PullRequest.ts index 5d0110b29..b0da72301 100644 --- a/packages/git/src/composition/components/PullRequest.ts +++ b/packages/git/src/composition/components/PullRequest.ts @@ -139,7 +139,7 @@ export const returns: ReturnsSchema = { export default function* PullRequest(props: Record): Operation { // Before anything of this element's own exists — before a Repository is - // observed, before a checkout is selected, before retained Push evidence is + // observed, before a checkout is selected, before saved Push evidence is // read and long before a Git host is contacted. The body is what the pull // request would say, and a body that never finished rendering must not become // one that was published. diff --git a/packages/git/src/composition/components/Repository.ts b/packages/git/src/composition/components/Repository.ts index e0ff4ce50..3443cfabf 100644 --- a/packages/git/src/composition/components/Repository.ts +++ b/packages/git/src/composition/components/Repository.ts @@ -16,7 +16,7 @@ * one. * * Where that checkout lives is the installed provider's. A workflow run holds it - * inside the run's own retained Workspace; an ordinary `xmd run` holds it under + * inside the run's own saved Workspace; an ordinary `xmd run` holds it under * the managed root on the caller's filesystem, protected for the execution by an * advisory lock. * diff --git a/packages/git/src/composition/errors.ts b/packages/git/src/composition/errors.ts index 384853cae..d85eaf850 100644 --- a/packages/git/src/composition/errors.ts +++ b/packages/git/src/composition/errors.ts @@ -17,7 +17,7 @@ * otherwise — for everything inside that region rather than for these * components alone. * - * A stale-state failure is the retained authoritative state disagreeing with + * A stale-state failure is the saved authoritative state disagreeing with * what attachment found. That is not something the document did, and not * something an authored region may decide either. */ @@ -74,7 +74,7 @@ export class WorktreeCompositionError extends Error { } /** - * Retained authoritative state disagrees with what attachment found. + * Saved authoritative state disagrees with what attachment found. * * A `StaleInputError`, which is what makes it fatal rather than printable: the * journal says this run has a checkout on a commit, and the Workspace root the @@ -83,7 +83,7 @@ export class WorktreeCompositionError extends Error { * history does not describe — and an enclosing `` must not be able * to downgrade that to a comment. * - * Nothing here repairs anything. Recloning would replace authoritative retained + * Nothing here repairs anything. Recloning would replace authoritative saved * state with whatever the current remote happens to hold, which is a different * run wearing this run's identity. */ @@ -94,7 +94,7 @@ export class RepositoryStaleStateError extends StaleInputError { constructor(subject: string, reason: string) { super( - `the retained Git state for ${subject} is not what this run recorded: ${reason}. ` + + `the saved Git state for ${subject} is not what this run recorded: ${reason}. ` + "The run is left exactly as it was found; nothing is recloned or repaired, and no " + "later work runs against state this run's history does not describe.", ); @@ -188,10 +188,10 @@ export class GitOperationError extends Error { } /** - * The observation an operation arrived with is not this run's retained state. + * The observation an operation arrived with is not this run's saved state. * * A Repository record that is not the row this run retains, a working directory - * inside no checkout it holds, a retained path that has stopped agreeing with + * inside no checkout it holds, a saved path that has stopped agreeing with * the identity naming it — none of these is something a document asked for and * did not get. Each is the run's own state disagreeing with what was presented * for it, so each fails the run the way a stale-state condition does: children @@ -200,7 +200,7 @@ export class GitOperationError extends Error { * * The sentence names the operation and the condition, and nothing else. What a * caller presented is not quoted back: a forged record is untrusted input, and - * an untrusted value belongs in a diagnostic even less than a retained one does. + * an untrusted value belongs in a diagnostic even less than a saved one does. */ export class GitOperationAdmissionError extends StaleInputError { override name = "GitOperationAdmissionError"; @@ -209,7 +209,7 @@ export class GitOperationAdmissionError extends StaleInputError { constructor(operation: string, reason: string) { super( - `${operation} is not admitted against this run's retained state: ${reason}. The run is ` + + `${operation} is not admitted against this run's saved state: ${reason}. The run is ` + "left exactly as it was found; no Git ran and no result was recorded for it.", ); this.operation = operation; @@ -231,17 +231,17 @@ export type PullRequestAdmissionReason = | "unreadable-push-evidence"; /** - * `` is not admitted by what this run retained. + * `` is not admitted by what this run saved. * * A `StaleInputError`, on the same terms as {@link GitOperationAdmissionError}: - * a document cannot avoid a Repository context that was replaced or a retained + * a document cannot avoid a Repository context that was replaced or a saved * Push result that stopped being readable, and later siblings must not run as * though a pull request had been opened. None of these is a Git-host answer — * every one of them is decided before anything is observed — so none of them is * printable and none of them is journaled as an effect outcome. * * The sentence names the category and, where there is one, the remedy. It - * quotes no journal content, no retained record and nothing a caller presented: + * quotes no journal content, no saved record and nothing a caller presented: * the evidence this refuses to read is exactly the evidence it must not repeat. */ export class PullRequestAdmissionError extends StaleInputError { @@ -251,7 +251,7 @@ export class PullRequestAdmissionError extends StaleInputError { constructor(reason: PullRequestAdmissionReason, sentence: string) { super( - ` is not admitted against this run's retained state: ${sentence} Nothing ` + + ` is not admitted against this run's saved state: ${sentence} Nothing ` + "was observed at the Git host, and no pull request was created.", ); this.reason = reason; diff --git a/packages/git/src/composition/git-push-records.ts b/packages/git/src/composition/git-push-records.ts index 6c500f3db..b9b1ed84b 100644 --- a/packages/git/src/composition/git-push-records.ts +++ b/packages/git/src/composition/git-push-records.ts @@ -10,7 +10,7 @@ * * ## The Repository travels filtered * - * The whole retained `RepositoryRecord` carries `checkoutPath` — a place inside + * The whole saved `RepositoryRecord` carries `checkoutPath` — a place inside * the run's own Workspace. A Git host has no business holding one, and the * journal has no reason to repeat it under a second name, so what reaches * durable JSON is the `RepositoryIdentity` a selection already carries. @@ -24,7 +24,7 @@ * that resource is named by the Repository, the remote and the destination ref. * A changed source commit is a different *request* — the durable fingerprint * covers the complete request, so it diverges rather than consuming the - * retained result — but it is not a different resource, and putting it in the + * saved result — but it is not a different resource, and putting it in the * natural key would say a branch moved to another commit is somewhere else. */ @@ -145,7 +145,7 @@ const RESULT_MEMBERS = [ * An object id in the algorithm this repository names its objects with. * * Width and case both. A value that is merely a non-empty string is not an - * object id, and reading one as an id is how a retained result that names + * object id, and reading one as an id is how a saved result that names * nothing passes for one that names a commit. */ export function gitObjectId(value: unknown, format: GitObjectFormat): string | undefined { @@ -319,7 +319,7 @@ export function parseGitPushObservations( * destination that is not that branch's ref, a refspec that is not this exact * commit published to that destination, and an observed commit that is not the * one this attempt asked the remote to hold. A Push that succeeded left the - * destination naming its source, so a retained result saying anything else + * destination naming its source, so a saved result saying anything else * describes an outcome this operation does not produce. */ export function parseGitPushResult( diff --git a/packages/git/src/composition/git-records.ts b/packages/git/src/composition/git-records.ts index 7adb4b2e2..8a56b8ac6 100644 --- a/packages/git/src/composition/git-records.ts +++ b/packages/git/src/composition/git-records.ts @@ -5,8 +5,8 @@ * checkout the enclosing `` and the contextual working directory * select, so what a component sends is the whole Repository record it observed * and the logical directory it observed — neither of which admits anything. - * The provider authenticates both against what this run retained; a record that - * is not the retained one, or a directory inside no retained checkout, is a + * The provider authenticates both against what this run saved; a record that + * is not the saved one, or a directory inside no saved checkout, is a * failure of admission rather than an outcome, and nothing is published for it. * * What comes back is evidence rather than a summary. The checkout the operation @@ -25,7 +25,7 @@ import type { Json } from "@executablemd/durable-streams"; import { beneath, canonicalWorkspacePath, members, optionalText, text } from "./parse.ts"; import type { GitObjectFormat, RepositoryRecord } from "./records.ts"; -/** Which retained checkout one Git operation ran in. */ +/** Which saved checkout one Git operation ran in. */ export interface GitCheckoutIdentity { readonly repositoryName: string; /** The Worktree's name, or `null` when the operation ran in the primary checkout. */ @@ -47,7 +47,7 @@ export interface GitCheckoutState { * * The Repository arrives whole rather than by name. A name alone can only be * looked up; the record can be *compared*, which is what lets the provider hold - * a replaced context to the exact row this run retained rather than to whichever + * a replaced context to the exact row this run saved rather than to whichever * Repository happens to answer to that name. * * The working directory is kept apart from the checkout throughout. A checkout @@ -64,7 +64,7 @@ export interface GitSwitchRequest { } /** - * What a completed `` retained. + * What a completed `` saved. * * The requested branch beside the branch the checkout ended on, and the * requested base beside the commit it actually started from — `null` when the @@ -101,7 +101,7 @@ const SWITCH_MEMBERS = [ * * Length and case both, because a digest is a fixed-width lowercase hex string * everywhere Git writes one. A value that is merely a non-empty string is not an - * object id, and treating one as an id is how a retained result that says + * object id, and treating one as an id is how a saved result that says * nothing passes for one that says something. */ function objectId(value: unknown, format: GitObjectFormat): string | undefined { @@ -254,7 +254,7 @@ export function parseGitSwitchResult( return undefined; } // Switching to the branch a checkout is already on moves nothing: no reset, - // no reference change, and nothing staged or unstaged. A retained result whose + // no reference change, and nothing staged or unstaged. A saved result whose // commit, HEAD tree or index tree changed across it describes a transition // Switch does not make. if ( @@ -324,7 +324,7 @@ export interface GitAddRequest { } /** - * What a completed `` retained. + * What a completed `` saved. * * The pathspecs exactly as they were given, and the checkout on both sides of * the command. Staging moves the index and nothing else, so the branch, the @@ -332,7 +332,7 @@ export interface GitAddRequest { * what makes that checkable rather than assumed — and the index tree is the one * value the operation may have changed. What was staged is not enumerated: * naming files Git discovered would put content beyond the document's own - * pathspecs into retained history. + * pathspecs into saved history. */ export interface GitAddResult { readonly checkout: GitCheckoutIdentity; @@ -371,7 +371,7 @@ function samePaths(left: readonly string[], right: readonly string[]): boolean { * The add result this value describes for this request, or `undefined`. * * Exact about membership and closed about meaning, on the same terms as every - * other retained result. Beyond the shape it refuses a result whose checkout is + * other saved result. Beyond the shape it refuses a result whose checkout is * not the one the request selects, whose pathspecs are not the ones it was * given — in the same order, with the same repetitions — and whose two readings * describe something other than staging: the branch, the commit and the HEAD @@ -442,7 +442,7 @@ export function parseGitCommitMessageSource(value: unknown): GitCommitMessageSou * The message arrives canonical, because canonicalization is a decision about * what a document meant and the component is where a document is read. What the * provider does with it is arithmetic: these are the bytes Git receives, and the - * bytes the retained digest describes. + * bytes the saved digest describes. * * `messageSource` travels beside it because the two are different facts. A * reader of the run's history can see that a message came from prose written @@ -459,9 +459,9 @@ export interface GitCommitRequest { } /** - * What a completed `` retained. + * What a completed `` saved. * - * The commit is described rather than quoted. Its message is retained as a + * The commit is described rather than quoted. Its message is saved as a * digest and a byte length instead of as text, because a run's history is read * by people and machines that have no business holding authored prose a second * time — and a digest is what proves the bytes Git committed are the bytes the @@ -530,7 +530,7 @@ function count(value: unknown): number | undefined { /** * The commit result this value describes for this request, or `undefined`. * - * Exact about membership and closed about meaning, like every retained result + * Exact about membership and closed about meaning, like every saved result * here. What it adds is the object graph: a commit of the index has exactly one * parent and that parent is where the checkout was, its tree is the tree the * index described, and the checkout ends on that commit with its HEAD tree and diff --git a/packages/git/src/composition/pull-request-operations.ts b/packages/git/src/composition/pull-request-operations.ts index b1601d9fe..eb80b9772 100644 --- a/packages/git/src/composition/pull-request-operations.ts +++ b/packages/git/src/composition/pull-request-operations.ts @@ -5,7 +5,7 @@ * `PullRequestAPI` is the transport surface — GitHub's middleware matches the * URLs it recognizes, holds a read to the host's ceiling, reconciles a create * or an update, and normalizes what comes back. This is the layer above it, and - * what it owns is *lifecycle and admission*: whether an answer is retained, + * what it owns is *lifecycle and admission*: whether an answer is saved, * what proves this run published the branch a pull request would name, and what * a second execution inherits. * @@ -68,7 +68,7 @@ export class PullRequestOperationsProviderError extends Error { super( `no pull-request provider is installed, so ${operation} cannot answer. The Deno and ` + "compiled `xmd run` entrypoints install the ordinary one; a workflow host installs the " + - "retained one for a live or partial execution.", + "saved one for a live or partial execution.", ); } } diff --git a/packages/git/src/composition/pull-request-read-execution.ts b/packages/git/src/composition/pull-request-read-execution.ts index 84ad5899e..a7abc2a09 100644 --- a/packages/git/src/composition/pull-request-read-execution.ts +++ b/packages/git/src/composition/pull-request-read-execution.ts @@ -11,13 +11,13 @@ * Once those three are answered, what to *do* with the admitted read is the * profile's business and no longer the adapter's. That is this boundary. The * adapter hands over the admitted read and the operation that would perform it; - * the installed policy decides whether performing it is retained. + * the installed policy decides whether performing it is saved. * * The base performs it afresh, which is both the ordinary profile's behaviour * and the honest default: a host that installed no lifecycle policy still reads * the collection, it simply keeps nothing. A workflow run composes middleware * here that wraps the same operation in one durable effect, so a completed one - * replays without a session and a failure after admission is retained as this + * replays without a session and a failure after admission is saved as this * read's own. * * The admitted read carries only what identifies the question — no run, no diff --git a/packages/git/src/composition/pull-request-read-records.ts b/packages/git/src/composition/pull-request-read-records.ts index dddbc86d6..ef9036c08 100644 --- a/packages/git/src/composition/pull-request-read-records.ts +++ b/packages/git/src/composition/pull-request-read-records.ts @@ -6,7 +6,7 @@ * are what "every existing review, comment and check result" means in a value. * Each is closed and provider-neutral: no raw provider response, live handle, * credential, endpoint or pagination transport state crosses this boundary, and - * every body and provider message is retained byte-for-byte because a reviewer + * every body and provider message is saved byte-for-byte because a reviewer * reading a summary of an objection has not read the objection. * * `id` is a decimal string. Provider identifiers already exceed what a JSON @@ -284,7 +284,7 @@ function evidenceJson(item: PullRequestEvidence): JsonObject { return record; } -/** The retained envelope, read back rather than asserted. */ +/** The saved envelope, read back rather than asserted. */ export function parsePullRequestReadResult(value: Json): PullRequestReadResult | undefined { if (!isJsonObject(value)) { return undefined; diff --git a/packages/git/src/composition/pull-request-records.ts b/packages/git/src/composition/pull-request-records.ts index 42cb45539..a818f7c6e 100644 --- a/packages/git/src/composition/pull-request-records.ts +++ b/packages/git/src/composition/pull-request-records.ts @@ -35,14 +35,14 @@ * * The complete request fingerprint still covers every input, so a changed * title, body, draft flag, base, head commit or number diverges at the durable - * position rather than consuming the result retained for another question. + * position rather than consuming the result saved for another question. * * ## The result is what the reconciliation settled on * * What a document binds is the pull request this effect left behind: the * provider's stable identity for it, its number, its URL, and the head and base * commits of the snapshot the reconciliation finished at. Reviews, checks, - * labels and later edits are separate reads. A retained record that re-read + * labels and later edits are separate reads. A saved record that re-read * them would be answering a different question every time it replayed. */ @@ -171,7 +171,7 @@ export interface PullRequestOutcome { } /** - * What a retained pull-request record is read back for. + * What a saved pull-request record is read back for. * * The complete admitted inputs, because every one of them is something the * record has to agree with: the resource it names, the immutable facts it was diff --git a/packages/git/src/composition/records.ts b/packages/git/src/composition/records.ts index b7ebb87c0..d93defdfd 100644 --- a/packages/git/src/composition/records.ts +++ b/packages/git/src/composition/records.ts @@ -9,10 +9,10 @@ * unversioned "current commit" column. * * A record names no locator and no host path. The locator a caller supplied is - * retained beside the record, where compatibility is decided; what travels + * saved beside the record, where compatibility is decided; what travels * through the journal and back to a document is its fingerprint. A fingerprint * discriminates a changed locator without publishing the bytes of one, which is - * what keeps a credential that slipped into a URL out of retained history even + * what keeps a credential that slipped into a URL out of saved history even * before the journal's own filter sees it. * * Every value here is parsed on the way back in. A replayed record arrives as diff --git a/packages/git/src/composition/selection.ts b/packages/git/src/composition/selection.ts index 6bf2e71b8..e6f4562af 100644 --- a/packages/git/src/composition/selection.ts +++ b/packages/git/src/composition/selection.ts @@ -4,7 +4,7 @@ * A `` has to know *something* about the checkout it commits in — * which repository it belongs to, which directory holds it, what its initial * branch was called. It must not know how that checkout came to exist. A - * workflow run's checkout is a row in a database, restored from a retained + * workflow run's checkout is a row in a database, restored from a saved * Workspace under a WorkflowRun the document must never be able to name; an * ordinary run's checkout is a directory on the caller's own filesystem, held * open by an advisory lock the provider owns. The components are the same @@ -17,7 +17,7 @@ * provider can read. Every operation authenticates the selection it was handed * against private state before it touches Git or a service, so a selection that * was copied, replaced or reconstructed can misname a target and cause a - * refusal — it cannot grant access to one. That is the same rule the retained + * refusal — it cannot grant access to one. That is the same rule the saved * Repository context has always followed, restated for a value two profiles * share. * @@ -36,7 +36,7 @@ import { parseObjectFormat, type GitObjectFormat, type RepositoryRecord } from " * A repository named without publishing where it came from. * * The locator is present as a fingerprint alone, so a credential that slipped - * into a URL is not repeated here, in retained Push JSON, or in the evidence a + * into a URL is not repeated here, in saved Push JSON, or in the evidence a * `` binds. Everything else is a fact about the repository itself: * the commit it was selected at, the branch it started on, and the algorithm it * names its objects with. @@ -87,7 +87,7 @@ export interface RepositorySelection { readonly checkoutPath: string; } -/** The retained record, filtered to the identity a selection carries. */ +/** The saved record, filtered to the identity a selection carries. */ export function filteredRepositoryIdentity(record: RepositoryRecord): RepositoryIdentity { return Object.freeze({ name: record.name, diff --git a/packages/git/src/deno/attachment.ts b/packages/git/src/deno/attachment.ts index ee9432c9d..fd9ac8a79 100644 --- a/packages/git/src/deno/attachment.ts +++ b/packages/git/src/deno/attachment.ts @@ -2,7 +2,7 @@ * What this package installs into one workflow run's Workspace. * * The block `@executablemd/workflow` used to hold: the Repository and Git - * composition providers, the retained lifecycles for the two service-reaching + * composition providers, the saved lifecycles for the two service-reaching * vocabularies, the component registrations and the Git-host middleware this * platform can reach. It is the same sequence in the same order — a run that * attaches it gets the providers it always had, beneath the run's own effect @@ -19,7 +19,7 @@ import type { Operation } from "effection"; import type { WorkflowWorkspaceAttachment } from "@executablemd/workflow/deno"; -import { useRetainedIssueOperations } from "../issue/effect.ts"; +import { useSavedIssueOperations } from "../issue/effect.ts"; import { denoRepositoryHost } from "./composition/host.ts"; import { denoGitHubSource } from "./composition/github-host.ts"; import { useGitHubPullRequests } from "./composition/pull-request.ts"; @@ -32,8 +32,8 @@ import { type CompositionProviderOptions, } from "./composition/provider.ts"; import { - useRetainedPullRequestOperations, - useRetainedPullRequestReads, + useSavedPullRequestOperations, + useSavedPullRequestReads, } from "./composition/pull-request-operations.ts"; import { useGitHubIssues, type GitHubIssuesOptions } from "./issue/github.ts"; @@ -109,13 +109,13 @@ export function gitWorkspaceAttachment( // once a destination is its own, and passes an unauthorized one on to // `IssueApi`'s base — the same answer installing nothing used to give. yield* useGitHubIssues({ host: denoGitHubSource, ...options.gitHubIssues }); - // The retained lifecycle for both service-reaching vocabularies, above + // The saved lifecycle for both service-reaching vocabularies, above // whichever transport middleware this host installed for them. - yield* useRetainedIssueOperations(); - yield* useRetainedPullRequestOperations(); + yield* useSavedIssueOperations(); + yield* useSavedPullRequestOperations(); // Durability for an admitted read, installed beside the transport rather // than above it: the adapter admits, this retains. - yield* useRetainedPullRequestReads(); + yield* useSavedPullRequestReads(); // Ordinary middleware, installed the way the Issue adapter is: it owns // the URLs it recognizes and delegates the rest. // Installed on every live or partial attachment, configured or not: the diff --git a/packages/git/src/deno/composition/add.ts b/packages/git/src/deno/composition/add.ts index b704ea4ea..388458b4e 100644 --- a/packages/git/src/deno/composition/add.ts +++ b/packages/git/src/deno/composition/add.ts @@ -6,7 +6,7 @@ * document wrote are handed to native Git in the order it wrote them, from the * directory the element was written in, as one command. * - * The retained result keeps those pathspecs and the checkout on both sides. + * The saved result keeps those pathspecs and the checkout on both sides. * Staging moves the index and nothing else, so the branch, the commit and the * HEAD tree are the same in both readings, and the index tree is the one value * that may have moved — or may not have, because staging what is already staged @@ -44,7 +44,7 @@ export const WORKSPACE_GIT_ADD = "workspace_git_add"; * The expansion makes two elements different effects and one element the same * effect across replays; the configuration fingerprint makes a document edited * to name other paths, another directory or another Repository diverge rather - * than replaying the previous one's retained result. + * than replaying the previous one's saved result. * * The pathspecs are carried entry by entry rather than joined. The fingerprint's * encoding is injective over a sequence, so two arrays that differ in order, in diff --git a/packages/git/src/deno/composition/authentication.ts b/packages/git/src/deno/composition/authentication.ts index fc21f176b..722112d47 100644 --- a/packages/git/src/deno/composition/authentication.ts +++ b/packages/git/src/deno/composition/authentication.ts @@ -1,13 +1,13 @@ /** * The authentication one live provider invocation borrows from its host. * - * A workflow's retained state says which repository an effect belongs to. It + * A workflow's saved state says which repository an effect belongs to. It * does not, and must not, say who this run is: an identity is a live property of * the machine the run is standing on, and a run resumed a week later on another * machine authenticates as whoever is there then. So nothing here is durable. A * session is opened for one provider invocation, shared by that invocation's * observations and its mutation, and disposed with it — a completed replay - * restores its retained result without reaching this module at all. + * restores its saved result without reaching this module at all. * * ## The adapter owns the credential, and owns it narrowly * @@ -16,7 +16,7 @@ * not do is let one out. The value reaches nothing but the private environment * of this invocation's own Git children, through a helper this provider wrote, * and it is not in an argument, a URL, a configuration file, a launcher, the - * ambient environment, a context, the Workspace, a journal, retained identity, + * ambient environment, a context, the Workspace, a journal, saved identity, * rendered output, a diagnostic or a failure cause. * * Acquisition asks the invoking user's ordinary helper chain once, about one @@ -233,13 +233,13 @@ export function sshCommand(home: string | undefined, agent: string | undefined): function pinned(ssh: string, lease: readonly string[]): readonly string[] { // The lease's own words, verbatim: it states a whole `-c` pair, because what // it installs is one helper and nothing else. Nothing the invoking user or a - // retained repository configured is copied here. + // saved repository configured is copied here. return [ "-c", `core.sshCommand=${ssh}`, // A redirect is a destination this run never authorized, and Git will carry // a credential it already holds across one to the same host — so the - // helper's exact-locator refusal is not enough on its own. The retained + // helper's exact-locator refusal is not enough on its own. The saved // locator is where this goes, or it does not go. "-c", "http.followRedirects=false", @@ -259,7 +259,7 @@ export interface CredentialRequest { /** * What one live provider invocation holds instead of a bare credential. * - * The value is retained lexically by the adapter, for this invocation only. A + * The value is saved lexically by the adapter, for this invocation only. A * caller can ask whether an identity was proved and whether the transport * rejected it, and can attach the invocation to a command — the credential * itself reaches nothing but the private environment of that command's own @@ -401,7 +401,7 @@ export interface GitAuthenticationOptions { * were asked about. A helper may rewrite those, and an answer about somewhere * else has authorized nothing. * - * What comes back is retained lexically here, for this invocation, and reaches + * What comes back is saved lexically here, for this invocation, and reaches * nothing but the private environment of this invocation's own Git children. */ export function denoCredentialBroker( diff --git a/packages/git/src/deno/composition/commit.ts b/packages/git/src/deno/composition/commit.ts index 21f4e6276..be52f8ce6 100644 --- a/packages/git/src/deno/composition/commit.ts +++ b/packages/git/src/deno/composition/commit.ts @@ -16,7 +16,7 @@ * different tree, or whose message is not byte-for-byte what was composed, is * not this invocation's commit, and nothing is imported or published for it. * - * The message is never retained. What is kept is a digest of its exact bytes and + * The message is never saved. What is kept is a digest of its exact bytes and * how many of them there were, which is what makes the read-back a proof and * keeps authored prose out of the run's own history. */ @@ -69,7 +69,7 @@ export interface GitCommitMessageEvidence { * UTF-8, because that is what Git receives and what a byte length has to be * counted in for the two to describe the same thing. Derived once, from the * admitted bytes, and then compared against everything: what Git read back, and - * what a retained result records. + * what a saved result records. */ export function gitCommitMessageEvidence(message: string): GitCommitMessageEvidence { const bytes = new TextEncoder().encode(message); @@ -144,7 +144,7 @@ export function* performCommit( /** * Who this commit is by, when it is not the fixed workflow identity. * - * Absent for a workflow run, whose retained Git state must not depend on + * Absent for a workflow run, whose saved Git state must not depend on * whose machine it was made on. Present for an ordinary run, where the commit * lands in the caller's own checkout. */ diff --git a/packages/git/src/deno/composition/effects.ts b/packages/git/src/deno/composition/effects.ts index b6bb2332f..6f0cf1b81 100644 --- a/packages/git/src/deno/composition/effects.ts +++ b/packages/git/src/deno/composition/effects.ts @@ -58,7 +58,7 @@ export type CompositionOutcome = { readonly kind: "created"; readonly record: Js /** * The transaction one composition effect works in. * - * The two halves a retained checkout is made of, bound together: what the + * The two halves a saved checkout is made of, bound together: what the * effect writes and what names it. Assembled here rather than threaded through * every function, so no step can reach one without the other. */ @@ -187,8 +187,8 @@ function* compositionEffect( /** * The refusal a restored failure describes, or the protocol failure it is. * - * A retained refusal carries a word, and a word outside the vocabulary this - * build speaks is not a refusal it may rebuild — it is a retained result this + * A saved refusal carries a word, and a word outside the vocabulary this + * build speaks is not a refusal it may rebuild — it is a saved result this * version cannot read, which is fatal rather than something to approximate. */ function refusal(kind: RefusalKind, name: string, reason: string): Error { diff --git a/packages/git/src/deno/composition/git.ts b/packages/git/src/deno/composition/git.ts index e0e9fc5ae..4ee35df84 100644 --- a/packages/git/src/deno/composition/git.ts +++ b/packages/git/src/deno/composition/git.ts @@ -11,7 +11,7 @@ * ## Resolution happens once * * A base is resolved and a commit pinned when the Repository is first reached, - * and the pin is what is retained. A remote whose default branch moves + * and the pin is what is saved. A remote whose default branch moves * afterwards changes nothing about this run: replay restores the commit rather * than asking again. That is why every resolution here reads a ref and returns * an object id — a name that could still be a name later is not an answer. @@ -22,7 +22,7 @@ * to mean anything against, so the provider always ends on a named branch. When * a supplied base is a tag or a bare commit rather than a branch, it creates one * with a deterministic provider-owned name and records that name, so the run's - * retained identity says which branch the checkout was made on rather than + * saved identity says which branch the checkout was made on rather than * leaving a reader to infer it. */ @@ -139,7 +139,7 @@ export function* objectFormat(git: GitSession, directory: string): Operation { @@ -598,8 +598,8 @@ export function* commitPresent( * * For a primary checkout this is its own `.git`; for a linked worktree it is * the repository the worktree is a worktree *of*. Comparing it with the place - * the enclosing Repository was materialized is what proves a retained worktree - * belongs to the retained Repository it names, rather than to some other + * the enclosing Repository was materialized is what proves a saved worktree + * belongs to the saved Repository it names, rather than to some other * repository whose administration happens to resolve. */ export function* commonDirectory( @@ -774,7 +774,7 @@ export function* readCommitMessage( * so `remote.origin.pushurl`, a `url..pushInsteadOf` rewrite, a * `pre-push` hook, a credential helper and `gpg.program` are all things a * pushed-from checkout could be asked to consult. Running the network operation - * there would let retained document data choose where this run publishes, what + * there would let saved document data choose where this run publishes, what * signs it, and what else runs while it happens. * * So the transport runs in a repository this provider just created, whose @@ -783,7 +783,7 @@ export function* readCommitMessage( * naming the selected checkout's own object database, which is what makes this * a different door onto the same objects rather than a second copy of them. * - * The object format is the retained record's. An alternate whose objects are + * The object format is the saved record's. An alternate whose objects are * named by another algorithm is not a database this repository can read. */ export function* initControlPlane( @@ -827,7 +827,7 @@ export type RemoteRefObservation = * reached" — which is the one confusion that would let an unreachable host be * read as proven absence and performed against. * - * The locator is the exact retained one and is given after `--`, so no + * The locator is the exact saved one and is given after `--`, so no * configured remote name, pushurl or rewrite takes part in where this looks. * * What comes back is lines a remote wrote, so it is parsed rather than read. diff --git a/packages/git/src/deno/composition/github.ts b/packages/git/src/deno/composition/github.ts index 230217a5c..3b0ec00b7 100644 --- a/packages/git/src/deno/composition/github.ts +++ b/packages/git/src/deno/composition/github.ts @@ -9,7 +9,7 @@ * closure §10.2 describes, and disposes it with that invocation. * * There is no provider registry and no negotiation. One adapter is selected by - * the retained locator, and a locator this adapter does not recognize is + * the saved locator, and a locator this adapter does not recognize is * answered as an unsupported effect kind before anything is sent — which is * exactly what a plain Git server that supports pushing and nothing else says. * @@ -622,7 +622,7 @@ export function nonEmpty(value: unknown): string | undefined { * It reads the eleven facts a snapshot holds plus the two a snapshot does not: * which repository the head lives in, and which the base does. A number is a * document's own word and a listing is filtered by a name, so neither proves - * that what came back belongs to the repository this run retained — the + * that what came back belongs to the repository this run saved — the * payload's own `full_name`s are what prove it. * * State is read rather than required. A closed pull request is a fact this diff --git a/packages/git/src/deno/composition/host.ts b/packages/git/src/deno/composition/host.ts index 5c55022c6..1a162a2c0 100644 --- a/packages/git/src/deno/composition/host.ts +++ b/packages/git/src/deno/composition/host.ts @@ -15,7 +15,7 @@ * Git reads the caller's environment, and almost everything it finds there can * change what a clone produces: `~/.gitconfig` can set `core.hooksPath`, * `init.templateDir` or a URL rewrite; `GIT_DIR` and `GIT_WORK_TREE` can point a - * command at a repository nobody named. A workflow's retained state must not + * command at a repository nobody named. A workflow's saved state must not * depend on whose machine it was created on, so the environment is built from * nothing and `HOME` points at the disposable materialization rather than at a * person. @@ -193,7 +193,7 @@ function environment( // The Workspace provider owns Git identity (spec §7.3). Without one Git // derives a name and an address from the operating-system user and the // machine's hostname and writes them into every reflog it touches, which - // would put whoever ran the host into the run's own retained Git state. + // would put whoever ran the host into the run's own saved Git state. // // An ordinary run supplies one instead, because the opposite is true there: // the commit lands in the caller's own checkout, and the fixed identity @@ -214,7 +214,7 @@ export interface RepositoryHostOptions { * What this host lends a remote-touching invocation. * * The trusted host decides which ambient mechanisms exist, which is why this - * is here rather than anywhere a document, a middleware or a retained record + * is here rather than anywhere a document, a middleware or a saved record * could reach. Absent, the shipped one is used. */ readonly authentication?: GitAuthentication; diff --git a/packages/git/src/deno/composition/identity.ts b/packages/git/src/deno/composition/identity.ts index 135d6ce30..ba41e3e4f 100644 --- a/packages/git/src/deno/composition/identity.ts +++ b/packages/git/src/deno/composition/identity.ts @@ -1,10 +1,10 @@ /** - * Holding a retained record to the identity that names it. + * Holding a saved record to the identity that names it. * * Placement is a function of identity — a name decides a slot, a slot decides a - * path — so a retained path is checkable rather than merely plausible. None of + * path — so a saved path is checkable rather than merely plausible. None of * it is this provider's word by the time it is read back: a column is whatever - * the database holds, and every disagreement found here is stale retained state + * the database holds, and every disagreement found here is stale saved state * rather than a refusal, because the document did not ask for it and could not * have avoided it. */ @@ -34,12 +34,12 @@ export type StaleReason = | "linkage"; const STALE_SENTENCES: ReadonlyMap = new Map([ - ["metadata", "the retained record naming it is gone or names something else"], + ["metadata", "the saved record naming it is gone or names something else"], ["checkout", "the Workspace no longer holds a checkout at the path it recorded"], ["administration", "what the Workspace holds there is no longer a readable Git checkout"], - ["repository", "the repository it belongs to is no longer retained"], + ["repository", "the repository it belongs to is no longer saved"], ["placement", "the path it recorded is not the one this run's placement gives its identity"], - ["locator", "the url retained for it is not the one its identity names"], + ["locator", "the url saved for it is not the one its identity names"], ["origin", "the checkout it holds came from a different repository"], ["object-format", "the checkout it holds names its objects with a different algorithm"], ["creation-commit", "the commit it was created at is not in the checkout it holds"], @@ -51,22 +51,22 @@ export function stale(subject: string, reason: StaleReason): RepositoryStaleStat } /** - * The retained record, once it still agrees with the identity that names it. + * The saved record, once it still agrees with the identity that names it. * * Placement is a function of identity: a name decides a slot, and the slot - * decides the path. So a retained path is checkable rather than merely + * decides the path. So a saved path is checkable rather than merely * plausible — recomputing it from the record's own name and comparing the whole * string is the difference between "this looks like a Workspace path" and "this * is the path this run gives this identity". * - * It matters because the retained path is not this provider's word by the time + * It matters because the saved path is not this provider's word by the time * it is read back. It is a column, and a column is whatever the database holds: * an edited row naming `/../../etc` would otherwise be joined to a host root and * followed out of the run's own tree. Checking here means the disagreement is * found before Git runs, before a child begins, and before anything is written. * * A disagreement is stale-state rather than a refusal, and deliberately so. The - * document did not ask for this and could not have avoided it; retained state + * document did not ask for this and could not have avoided it; saved state * says something this run cannot have written, and nothing repairs that. */ export function agreedRepository(record: RepositoryRecord, subject: string): RepositoryRecord { @@ -84,7 +84,7 @@ export function agreedWorktree(record: WorktreeRecord, subject: string): Worktre } /** - * The locator retained for a Repository, once it is still the one it names. + * The locator saved for a Repository, once it is still the one it names. * * Two things are checked and they are different. The bytes must still be * admissible — a stored locator that this provider would refuse today is not one @@ -104,7 +104,7 @@ export function agreedLocator(stored: StoredRepository, subject: string): string return stored.locator; } -/** A retained Repository row, once its path and its locator both still agree. */ +/** A saved Repository row, once its path and its locator both still agree. */ export function agreedStored(stored: StoredRepository, subject: string): StoredRepository { agreedRepository(stored.record, subject); agreedLocator(stored, subject); @@ -114,7 +114,7 @@ export function agreedStored(stored: StoredRepository, subject: string): StoredR /** * Whether this checkout identity is the one this run's placement gives that name. * - * The half of a retained result the shared protocol cannot check. Placement is a + * The half of a saved result the shared protocol cannot check. Placement is a * function of identity and the function is this provider's, so a name and a path * that arrive together have to still be the pair placement produces: a result * naming one Worktree at another's path, or naming a Worktree at all where the @@ -153,7 +153,7 @@ export function worktreeSubject(name: string): string { * * `repositoryDirectory` is where the Repository this checkout belongs to was * materialized — for a primary checkout, itself — and `repository` is that - * Repository's retained record. Both travel with the directory because + * Repository's saved record. Both travel with the directory because * verification is a comparison, and neither half of it can be read off the host * tree alone. */ diff --git a/packages/git/src/deno/composition/locator.ts b/packages/git/src/deno/composition/locator.ts index 7b9198d60..cdfbeb3fa 100644 --- a/packages/git/src/deno/composition/locator.ts +++ b/packages/git/src/deno/composition/locator.ts @@ -6,7 +6,7 @@ * here and they are different questions. **Admission** decides whether this * provider will hand the string to Git at all. **Fingerprinting** produces the * stable name the journal, the record and every compatibility comparison use, - * so a changed locator diverges without the bytes of either one being retained + * so a changed locator diverges without the bytes of either one being saved * outside the single column that holds them. * * Admission is a closed allowlist rather than a search for bad shapes. Git's @@ -84,7 +84,7 @@ export function admitLocator(locator: string): string | undefined { } // A local path. Absolute only: a relative one would name a different // repository depending on which directory the host happened to run in, and a - // workflow's retained identity must not depend on that. + // workflow's saved identity must not depend on that. if (locator.startsWith("/")) { return locator; } diff --git a/packages/git/src/deno/composition/materialize.ts b/packages/git/src/deno/composition/materialize.ts index 5d5b3f6f0..572cf6ce8 100644 --- a/packages/git/src/deno/composition/materialize.ts +++ b/packages/git/src/deno/composition/materialize.ts @@ -47,7 +47,7 @@ const GITDIR_PREFIX = "gitdir: "; * The subtraction the module is built on only works in one direction for free. * Removing a root from an absolute path always leaves something; prepending a * root to an arbitrary string does not always land beneath it, and a Workspace - * path that arrived from a retained row is not this provider's own word — a + * path that arrived from a saved row is not this provider's own word — a * database somebody edited can hold `/../../etc`, and concatenation would walk * out of the disposable tree and into the host. * @@ -57,7 +57,7 @@ const GITDIR_PREFIX = "gitdir: "; function hostPath(root: string, workspacePath: string): string { if (canonicalWorkspacePath(workspacePath) === undefined) { throw new RepositoryStaleStateError( - "a retained checkout", + "a saved checkout", "the Workspace path it recorded does not name one place beneath the Workspace root", ); } @@ -133,7 +133,7 @@ function* exportEntry( } /** - * Export one retained checkout into ``, parents included. + * Export one saved checkout into ``, parents included. * * ## Why the root is not an ordinary entry * @@ -149,7 +149,7 @@ function* exportEntry( * state the database does not hold — which is exactly the guarantee the * Workspace exists to make. * - * So the root must be a real retained directory, and the export must land where + * So the root must be a real saved directory, and the export must land where * it was aimed. Both are decided here, before Git runs and before any * administration path is rewritten, because either one is what the next step * would trust. @@ -168,7 +168,7 @@ export function* exportTree( throw new RepositoryStaleStateError( subject, "what the Workspace holds at the path it recorded is not a directory, so the checkout " + - "this run would operate on is not the one it retained", + "this run would operate on is not the one it saved", ); } @@ -344,7 +344,7 @@ function* worktreePointer(worktreeHostPath: string, subject: string): Operation< refuse( subject, "the `.git` in the worktree it holds is not a real file, so the administration native " + - "Git would follow is not the one this run retained", + "Git would follow is not the one this run saved", ); } return pointer; @@ -364,15 +364,15 @@ function* pointerValue(file: string): Operation { } /** - * The Workspace path a retained pointer names, once it names one at all. + * The Workspace path a saved pointer names, once it names one at all. * - * A retained pointer holds a Workspace path, because that is what + * A saved pointer holds a Workspace path, because that is what * canonicalization left there. Relative, empty, dot-segmented and * traversal-shaped values are all refused rather than resolved: each of them * names an administration directory somewhere other than where this run put one, * and prefixing a root onto it would produce a path that leaves the export. */ -function* retainedPointer(file: string, subject: string): Operation { +function* savedPointer(file: string, subject: string): Operation { const value = canonicalWorkspacePath(yield* pointerValue(file)); if (value === undefined) { refuse( @@ -400,7 +400,7 @@ function* agreedPair( subject: string, ): Operation { const pointer = yield* worktreePointer(worktreeHostPath, subject); - const named = `${root}${yield* retainedPointer(pointer, subject)}`; + const named = `${root}${yield* savedPointer(pointer, subject)}`; const slot = [...control.slots.values()].find((candidate) => candidate === named); if (slot === undefined) { refuse( @@ -410,12 +410,12 @@ function* agreedPair( } // Unconditional: every slot reaching here has been proven to hold a real // pointer, so there is no absence for a comparison to be skipped over. - if (`${root}${yield* retainedPointer(`${slot}/gitdir`, subject)}` !== pointer) { + if (`${root}${yield* savedPointer(`${slot}/gitdir`, subject)}` !== pointer) { refuse(subject, "its administration directory names a different worktree"); } } -/** Put a materialization root back in front of every retained administration path. */ +/** Put a materialization root back in front of every saved administration path. */ /** * Every administration file a rewrite may touch, and nothing else. * @@ -448,7 +448,7 @@ export function* localizeAdministration( } for (const file of yield* administrationFiles(control, worktreeHostPaths, subject)) { - const value = yield* retainedPointer(file, subject); + const value = yield* savedPointer(file, subject); const content = yield* readTextFile(file); yield* writeTextFile( file, @@ -460,7 +460,7 @@ export function* localizeAdministration( /** * Take the materialization root back out of every administration path. * - * What is left is a Workspace path, which is what may be retained. A value that + * What is left is a Workspace path, which is what may be saved. A value that * does not begin with the root is refused rather than left alone: Git wrote an * administration path this provider did not predict, and retaining it would keep * a host path — or a pointer to somewhere else entirely — in the run. @@ -482,8 +482,8 @@ export function* canonicalizeAdministration( "run materialized it in", ); } - const retained = canonicalWorkspacePath(value.slice(root.length)); - if (retained === undefined) { + const saved = canonicalWorkspacePath(value.slice(root.length)); + if (saved === undefined) { refuse( subject, "a linked-worktree administration pointer does not name one place beneath the " + @@ -493,7 +493,7 @@ export function* canonicalizeAdministration( const content = yield* readTextFile(file); yield* writeTextFile( file, - rewriteLine(content, () => retained), + rewriteLine(content, () => saved), ); } } diff --git a/packages/git/src/deno/composition/object-source.ts b/packages/git/src/deno/composition/object-source.ts index abe009c36..069891efe 100644 --- a/packages/git/src/deno/composition/object-source.ts +++ b/packages/git/src/deno/composition/object-source.ts @@ -5,7 +5,7 @@ * Workspace retains and a document can write, and several ordinary settings in * one name a program or a destination: `remote.origin.pushurl`, a * `url..pushInsteadOf` rewrite, a `pre-push` hook, a credential helper, - * `gpg.program`. Running the transport there would let retained document data + * `gpg.program`. Running the transport there would let saved document data * choose where this run publishes and what else runs while it happens. * * So the transport runs in a bare repository this provider created, configured @@ -35,7 +35,7 @@ * Nothing here runs until a live provider first needs the source, which is * before its first remote observation and after every local admission check. An * execution that replays a completed push reaches no provider at all: the - * shared engine hands back the retained record, so no control repository is + * shared engine hands back the saved record, so no control repository is * built, no object graph is walked and no Git runs for the push. */ @@ -108,7 +108,7 @@ function* containedTree(directory: string, operation: string): Operation { uncontained( operation, "its object database holds a symbolic link, so what native Git would read is not what " + - "this run retained", + "this run saved", ); } if (info.isDirectory()) { @@ -409,7 +409,7 @@ function* containedAlternates( * The object database this checkout publishes from, once it is contained. * * Three questions in one order, and the order is what makes each answerable. - * The database has to be the retained Repository's own — a linked worktree + * The database has to be the saved Repository's own — a linked worktree * shares it, which is why one proof covers both kinds of checkout. It has to be * a real directory that resolves to itself, so the walk below is walking the * export rather than somewhere a link led. And then everything Git may traverse @@ -421,13 +421,13 @@ function* containedObjectDatabase(checkout: GitCheckout, operation: string): Ope if ((yield* commonDirectory(checkout.git, checkout.directory)) !== administration) { uncontained( operation, - "the checkout it ran in does not share the retained repository's object database", + "the checkout it ran in does not share the saved repository's object database", ); } const objects = `${administration}/objects`; if ((yield* entry(objects))?.isDirectory() !== true) { - uncontained(operation, "the retained repository holds no object database to publish from"); + uncontained(operation, "the saved repository holds no object database to publish from"); } if ((yield* until(realpath(objects))) !== objects) { uncontained( diff --git a/packages/git/src/deno/composition/operations.ts b/packages/git/src/deno/composition/operations.ts index 67c951837..9039f8109 100644 --- a/packages/git/src/deno/composition/operations.ts +++ b/packages/git/src/deno/composition/operations.ts @@ -12,7 +12,7 @@ * A document says which checkout it means by where it writes the element: the * enclosing `` supplies the record, the contextual working directory * supplies the place. Neither is trusted. The record is compared member for - * member against the row this run retained under that name, every retained row + * member against the row this run saved under that name, every saved row * is held to the identity that names it, and the working directory has to be a * place inside one of the checkouts those rows carry. * @@ -26,8 +26,8 @@ * A checkout is what the operation belongs to; the working directory is where * inside it the element was written. `` inside a * Repository still operates on that Repository — it is where Git runs, not what - * Git runs on. So the two travel separately: identity comes from the retained - * row, and the place is the retained checkout's own path with the rest of the + * Git runs on. So the two travel separately: identity comes from the saved + * row, and the place is the saved checkout's own path with the rest of the * directory joined onto it, proven to be a real directory inside the export * before any command is given it. * @@ -42,7 +42,7 @@ * * ## Verified before, evidence after * - * Between materialization and native Git, the retained identity is proven + * Between materialization and native Git, the saved identity is proven * against what was exported: placement, origin, object format, creation commit, * and — for a linked worktree — that it really is a worktree of the Repository * it names. Then the checkout's branch, commit, HEAD tree and index tree are @@ -101,7 +101,7 @@ import { worktreeDisagreement } from "./worktree.ts"; /** * The digest one Git operation's durable identity is built from. * - * Injective, because the values it covers include a whole retained record and a + * Injective, because the values it covers include a whole saved record and a * document's own strings, and durable identity is what decides whether a * recorded result may be handed back for an invocation. Two different * observations that digested alike would let a replay return a transition @@ -124,7 +124,7 @@ import { worktreeDisagreement } from "./worktree.ts"; * units the encoding writes. * * This is deliberately its own encoding rather than a change to - * `fingerprintOf()`, whose digests are already part of retained Repository and + * `fingerprintOf()`, whose digests are already part of saved Repository and * Worktree identity in this run's history. */ export function gitOperationFingerprint(values: readonly (string | null)[]): string { @@ -172,10 +172,10 @@ export interface GitCheckout { } /** - * Which retained checkout one operation runs in, and what travels with it. + * Which saved checkout one operation runs in, and what travels with it. * * Produced inside a Workspace transaction and consumed outside one, because the - * two halves need different things: choosing a checkout reads retained rows, + * two halves need different things: choosing a checkout reads saved rows, * and working in it reads a host tree. Both local Git effects and Push select * this way, so the selection is a value rather than a step inside either. */ @@ -195,12 +195,12 @@ function refuseAdmission(operation: string, reason: string): never { } /** - * Which retained checkout the observed Repository and working directory select. + * Which saved checkout the observed Repository and working directory select. * * Every row read here is held to the identity that names it before it is used * for anything, so a placement or a locator that no longer agrees is found * before a host path is joined and before Git exists in the story. The observed - * record is then compared with the retained one member for member: a name can + * record is then compared with the saved one member for member: a name can * only be looked up, and looking one up is what a replaced context would rely * on. */ @@ -219,7 +219,7 @@ export function selectGitCheckout( if (!sameRepositoryRecord(stored.record, observed)) { refuseAdmission( operation, - "the Repository it observed is not the one this run retained under that name", + "the Repository it observed is not the one this run saved under that name", ); } @@ -294,7 +294,7 @@ export function* checkoutState( /** * The real directory inside the export the element was written in. * - * Proven rather than assumed, at the one place the retained path and the host + * Proven rather than assumed, at the one place the saved path and the host * root are joined. The checkout root already resolves to itself; what is added * here is the rest of the working directory, which may name anything a checkout * contains — including a tracked symbolic link, which the operating system would @@ -349,12 +349,12 @@ function* entry(path: string): Operation { } /** - * That the exported checkout is still the one the retained record names. + * That the exported checkout is still the one the saved record names. * * Creation identity, read back out of the bytes: where it came from, how it * names objects, the commit it was created at, and — for a linked worktree — * that it is a worktree of the Repository it belongs to. A disagreement is stale - * retained state, which is fatal: nothing published, nothing repaired. + * saved state, which is fatal: nothing published, nothing repaired. */ function* disagreement( git: GitSession, @@ -373,7 +373,7 @@ function* disagreement( } } -/** Where one retained Repository/Worktree family landed on the host. */ +/** Where one saved Repository/Worktree family landed on the host. */ export interface ExportedCheckouts { readonly repositoryDirectory: string; /** The selected checkout's own directory: the Repository's, or a Worktree's. */ @@ -382,7 +382,7 @@ export interface ExportedCheckouts { } /** - * Write the whole retained family into a host tree Git can be pointed at. + * Write the whole saved family into a host tree Git can be pointed at. * * The whole family, even when the operation runs in one checkout of it. Git * decides what a repository's worktrees are by reading its own record of them, @@ -432,7 +432,7 @@ export function* exportCheckoutFamily( } /** - * The exported checkout, once it is usable and once it is the retained one. + * The exported checkout, once it is usable and once it is the saved one. * * Three things in one order, and the order is the point. The administration is * localized first, because until it is, a linked worktree's pointers still name diff --git a/packages/git/src/deno/composition/placement.ts b/packages/git/src/deno/composition/placement.ts index 96baa9e5f..db42bf357 100644 --- a/packages/git/src/deno/composition/placement.ts +++ b/packages/git/src/deno/composition/placement.ts @@ -9,8 +9,8 @@ * A slot is therefore a readable stem and a digest of the whole identity. The * digest is what makes placement collision-free — two distinct identities have * distinct slots even when their stems collapse to the same characters — and the - * stem is what makes a retained path legible to somebody reading the database. - * Neither half is parsed back: the retained `checkout_path` column is the + * stem is what makes a saved path legible to somebody reading the database. + * Neither half is parsed back: the saved `checkout_path` column is the * record of where a checkout is, and this is only how the first one was * chosen. */ @@ -38,7 +38,7 @@ export function repositoryCheckoutPath(name: string): string { * * A linked worktree inside the repository it belongs to would be walked by that * repository's own status and search, and importing one tree would then import - * the other. Keeping them siblings makes each retained tree exactly one + * the other. Keeping them siblings makes each saved tree exactly one * checkout. */ export function worktreeCheckoutPath(repositoryName: string, name: string): string { diff --git a/packages/git/src/deno/composition/provider.ts b/packages/git/src/deno/composition/provider.ts index 4348eb742..a698ab097 100644 --- a/packages/git/src/deno/composition/provider.ts +++ b/packages/git/src/deno/composition/provider.ts @@ -5,7 +5,7 @@ * to the two components' own modules. `repository.ts` and `worktree.ts` own what * a `` and a `` respectively create and attach; * `effects.ts` owns the durable envelope both perform inside; `refusals.ts` owns - * the words a refusal travels in; `identity.ts` owns holding a retained record + * the words a refusal travels in; `identity.ts` owns holding a saved record * to the identity that names it. * * What is left here is the pairing of a creation with an attachment, the @@ -133,7 +133,7 @@ export function workflowSelections(): SelectionRegistry { } /** - * Rebuild a live checkout from the Workspace and prove it is the retained one. + * Rebuild a live checkout from the Workspace and prove it is the saved one. * * The transaction is held only for the export: Git runs afterwards, against * files, so a subprocess never keeps the run's database locked. Nothing here @@ -182,7 +182,7 @@ function hostOf(options: CompositionProviderOptions): RepositoryHost { function unselected(operation: string): GitOperationAdmissionError { return new GitOperationAdmissionError( operation, - "the Repository in scope is not one this run selected, so it names no retained checkout", + "the Repository in scope is not one this run selected, so it names no saved checkout", ); } diff --git a/packages/git/src/deno/composition/pull-request-operations.ts b/packages/git/src/deno/composition/pull-request-operations.ts index bb63af11c..2b1893cfd 100644 --- a/packages/git/src/deno/composition/pull-request-operations.ts +++ b/packages/git/src/deno/composition/pull-request-operations.ts @@ -66,7 +66,7 @@ import { parseJsonValue } from "@executablemd/workflow"; import { getWorkflowRun } from "@executablemd/workflow"; import { gitOperationFingerprint } from "./operations.ts"; -/** The durable effect type one evidence read is retained under. */ +/** The durable effect type one evidence read is saved under. */ export const PULL_REQUEST_READ = "pull_request_read"; /** Which element a refusal names, by the collection it was reading. */ @@ -78,7 +78,7 @@ const ELEMENT: Readonly> = Object.freeze({ function* describeRead(request: PullRequestReadRequest): Operation { const expansion = yield* getExpansion(); - // The run is in the retained request, and deliberately not in this + // The run is in the saved request, and deliberately not in this // fingerprint. A fork is a different run reaching the same position with the // same question, and a name that carried the run would make every inherited // read a different effect — which is to say, unforkable. What the name has to @@ -100,7 +100,7 @@ function* describeRead(request: PullRequestReadRequest): Operation { +export function useSavedPullRequestReads(): Operation { return PullRequestReadExecution.around( { *perform([admitted, transport]): Operation { @@ -180,8 +180,8 @@ export function useRetainedPullRequestReads(): Operation { ); } -/** Install the retained pull-request lifecycle for the current scope and below. */ -export function useRetainedPullRequestOperations(): Operation { +/** Install the saved pull-request lifecycle for the current scope and below. */ +export function useSavedPullRequestOperations(): Operation { return PullRequestOperations.around( { // Straight through, so the adapter beneath decides when a read is diff --git a/packages/git/src/deno/composition/pull-request-reads.ts b/packages/git/src/deno/composition/pull-request-reads.ts index 0333d315f..859d62a2d 100644 --- a/packages/git/src/deno/composition/pull-request-reads.ts +++ b/packages/git/src/deno/composition/pull-request-reads.ts @@ -16,7 +16,7 @@ * * ## Transport, and only transport * - * Nothing here is retained. What a read *costs* — whether it is performed once + * Nothing here is saved. What a read *costs* — whether it is performed once * and kept, or performed afresh every execution — is selected through * `PullRequestReadExecution`, whose base performs the transport and around * which a workflow run installs the policy that makes an admitted read durable. @@ -259,7 +259,7 @@ function resolver( /** * Install GitHub pull-request reading for the current scope and below. * - * Both profiles install exactly this. What a read *costs* — retained once, or + * Both profiles install exactly this. What a read *costs* — saved once, or * performed afresh every execution — is decided above it, at * `PullRequestOperations`; what is decided here is which URLs this host will * read at all and what a credential may see. @@ -319,7 +319,7 @@ export function* useGitHubPullRequestReads( } // Admitted. Everything above decided whether this host may answer at all, - // and none of it is retained: a target outside the ceiling, or one this + // and none of it is saved: a target outside the ceiling, or one this // adapter cannot name, leaves no record of a question that was never // permitted. // diff --git a/packages/git/src/deno/composition/pull-request.ts b/packages/git/src/deno/composition/pull-request.ts index 384df6ad7..171bb5cb1 100644 --- a/packages/git/src/deno/composition/pull-request.ts +++ b/packages/git/src/deno/composition/pull-request.ts @@ -12,7 +12,7 @@ * 1. The request is detached, so nothing the caller does to its own objects * afterwards reaches the effect this describes. * 2. One short read-only Workspace transaction authenticates the observed - * Repository record and working directory against the retained rows and + * Repository record and working directory against the saved rows and * exports the selected checkout family. * 3. The transaction closes. Everything after it reads files. * 4. The exported checkout is prepared and authenticated, and its current @@ -150,7 +150,7 @@ function* admitInputs( * parsed. * * Ordered so that everything a later step trusts has already been proven: the - * retained rows and the export inside one short transaction, the exported + * saved rows and the export inside one short transaction, the exported * checkout's identity outside it, the branch and commit from that checkout, * this run's own proof that it published them, and only then a frozen request * and a provider that can answer for it. @@ -207,7 +207,7 @@ export function* upsertPullRequest( const events = yield* database.journal.readAll(); admitPushEvidence(events, inputs); - // The exact retained locator, authenticated against its own fingerprint + // The exact saved locator, authenticated against its own fingerprint // when the row was read, rather than a `remote.origin.url` out of a // configuration file this run merely stores. It stays in the provider's // closure and reaches no durable or public value. @@ -277,7 +277,7 @@ export function* useGitHubPullRequests( () => new GitOperationAdmissionError( PULL_REQUEST_ELEMENT, - "the Repository in scope is not one this run selected, so it names no retained " + + "the Repository in scope is not one this run selected, so it names no saved " + "checkout", ), ), diff --git a/packages/git/src/deno/composition/push.ts b/packages/git/src/deno/composition/push.ts index ecdd2e1ed..a832fe716 100644 --- a/packages/git/src/deno/composition/push.ts +++ b/packages/git/src/deno/composition/push.ts @@ -12,8 +12,8 @@ * ## What happens before the remote exists in the story * * Admission first, and all of it locally. The enclosing `` and the - * contextual working directory select a retained checkout the way they do for - * every other Git operation; the observed record is compared with the retained + * contextual working directory select a saved checkout the way they do for + * every other Git operation; the observed record is compared with the saved * row member for member, every row is held to the identity naming it, and the * exported family is proven to be the checkout the record describes. Only then * are the branch and the commit read — from the checkout, never from a @@ -39,7 +39,7 @@ * ## What the provider is given, and what it is not * * The transport needs the objects and the destination, and nothing else may - * travel with them. The retained locator, the disposable control repository and + * travel with them. The saved locator, the disposable control repository and * the object database live in this module's own closure: public Git-host * middleware sees the frozen JSON request #297 defines and no part of any of * them. The durable request carries the Repository's filtered identity without @@ -161,7 +161,7 @@ function* provenAncestor( } /** - * The Workspace root this invocation's own retained Push was written against, + * The Workspace root this invocation's own saved Push was written against, * or `undefined` when it has none and this is a live attempt. * * A Push names its request from the checkout: the branch it is on and the @@ -174,7 +174,7 @@ function* provenAncestor( * So a completed Push is reconstructed from the root its own journal row was * appended against. That association was made when the event was written and is * the only thing here that says where this position was; the current pointer, - * the branch name and the retained payload all describe somewhere else, or + * the branch name and the saved payload all describe somewhere else, or * describe it circularly. * * Reading history is all this does. The root it names still has to produce the @@ -182,7 +182,7 @@ function* provenAncestor( * one refuses the replay rather than answering it, and no root selected here * can admit live Git-host work. */ -function* retainedPushRoot( +function* savedPushRoot( database: WorkflowRunDatabase, expansionId: string, repository: RepositoryIdentity, @@ -207,7 +207,7 @@ function* retainedPushRoot( : undefined; // A record this position cannot read as a Push of the Repository it // admitted is not one to reconstruct from. It is not read around either: - // the shared engine holds every retained record at this position to the + // the shared engine holds every saved record at this position to the // request being made, and refuses one that answers a different question. if (inputs === undefined || !sameRepositoryIdentity(inputs.repository, repository)) { continue; @@ -223,25 +223,25 @@ function* retainedPushRoot( /** * The selection and the checkout family this invocation names its request from. * - * One inspection, at the root this invocation is speaking from. A retained Push + * One inspection, at the root this invocation is speaking from. A saved Push * names the moment it published, and reading the Workspace as it was then — * without the run returning to it — is the inspection boundary's own * responsibility: it materializes that root, hands it to this callback, and * rolls the materialization back however the callback ends. * - * The rows the selection reads are the same either way. Retained Repository and + * The rows the selection reads are the same either way. Saved Repository and * Worktree identity is creation identity, which a Workspace root does not hold * and a restoration does not move. */ function prepareRequestSource( database: WorkflowRunDatabase, - retained: string | undefined, + saved: string | undefined, root: string, admitted: GitOperationRequest, ): Operation> { return readWorkflowWorkspace( database, - retained === undefined ? {} : { rootId: retained }, + saved === undefined ? {} : { rootId: saved }, function* (workspace) { const selection = selectGitCheckout(readWorkspaceMetadata(workspace.storage), PUSH, admitted); return { @@ -256,7 +256,7 @@ function prepareRequestSource( * The provider that answers this exact reconciliation, and no other. * * Installed around one `reconcileGitHostEffect()` call and reachable only from - * inside it. Its closure holds the retained locator and the object source; what + * inside it. Its closure holds the saved locator and the object source; what * it receives from the engine is the frozen request, which it parses and holds * to the inputs this invocation admitted. A request naming another Repository, * branch or commit is not this invocation's, and answering one would publish a @@ -416,7 +416,7 @@ function* admitInputs(checkout: GitCheckout, admitted: GitPushRequest): Operatio * The whole of what `` asks for: one reconciled effect, exactly parsed. * * Ordered so that everything a later step trusts has already been proven: the - * retained rows and the export inside one short transaction, the exported + * saved rows and the export inside one short transaction, the exported * checkout's identity outside it, the branch and commit from that checkout, and * only then a frozen request and a provider that can answer for it. */ @@ -441,10 +441,10 @@ export function* createGitPush( const git = gitSession(host, root); // Which moment this invocation is naming its request from. Read before the - // transaction, because it is a question about retained history rather than + // transaction, because it is a question about saved history rather than // about the Workspace, and a transaction here cannot nest inside itself. const expansion = yield* getExpansion(); - const retained = yield* retainedPushRoot( + const saved = yield* savedPushRoot( database, expansion.id, filteredRepositoryIdentity(admitted.repository), @@ -452,7 +452,7 @@ export function* createGitPush( // Held open for the export alone. Everything after this reads files, and a // remote round trip must never keep the run's database locked. - const prepared = yield* prepareRequestSource(database, retained, root, admitted); + const prepared = yield* prepareRequestSource(database, saved, root, admitted); if (!prepared.ok) { throw prepared.error; } @@ -461,7 +461,7 @@ export function* createGitPush( const checkout = yield* prepareCheckout(root, git, selection, exported, PUSH); const inputs = yield* admitInputs(checkout, admitted); - // The exact retained locator, authenticated against its own fingerprint + // The exact saved locator, authenticated against its own fingerprint // when the row was read, rather than `remote.origin.url` or a `pushurl` out // of a configuration file this run merely stores. const locator = selection.repository.locator; diff --git a/packages/git/src/deno/composition/refusals.ts b/packages/git/src/deno/composition/refusals.ts index 371aaae9b..38069503c 100644 --- a/packages/git/src/deno/composition/refusals.ts +++ b/packages/git/src/deno/composition/refusals.ts @@ -157,10 +157,10 @@ export function gitRefusal(operation: string, reason: GitFailureReason): GitOper } /** - * The refusal a retained failure name describes, or `undefined` for none. + * The refusal a saved failure name describes, or `undefined` for none. * * A word outside the closed set is not a refusal this build may rebuild. It is a - * retained result that says something this version cannot read, and the caller + * saved result that says something this version cannot read, and the caller * treats it as one rather than receiving a plausible refusal assembled from a * word nobody wrote. */ diff --git a/packages/git/src/deno/composition/repository.ts b/packages/git/src/deno/composition/repository.ts index db60b9f6e..01f595199 100644 --- a/packages/git/src/deno/composition/repository.ts +++ b/packages/git/src/deno/composition/repository.ts @@ -73,7 +73,7 @@ import { * The expansion is what makes two elements different effects and one element * the same effect across replays. The configuration fingerprint is what makes a * document edited to name another repository, base or branch diverge rather than - * quietly replaying the previous one's retained checkout — durable identity is + * quietly replaying the previous one's saved checkout — durable identity is * type and name, so the fingerprint belongs in the name rather than beside it. * The locator reaches it only through its own fingerprint, so a credential that * was written into a URL is not in an effect description either. @@ -176,7 +176,7 @@ export function* prepareRepositoryAttachment( if (stored === undefined || !sameRepositoryRecord(stored.record, record)) { throw stale(subject, "metadata"); } - // Before the Workspace is read and long before Git runs: what is retained has + // Before the Workspace is read and long before Git runs: what is saved has // to still be this identity's own placement and this identity's own url. agreedStored(stored, subject); const checkoutPath = agreedRepository(record, subject).checkoutPath; @@ -189,10 +189,10 @@ export function* prepareRepositoryAttachment( } /** - * What the checkout at a retained path has to turn out to be, or what disagrees. + * What the checkout at a saved path has to turn out to be, or what disagrees. * * Readable is where this used to stop, and readable is a property of *any* - * repository. A clone of an unrelated repository written over the retained path + * repository. A clone of an unrelated repository written over the saved path * is perfectly readable, so attachment that asked only that question accepted * one identity's history against another identity's bytes — and the document * carried on, restoring recorded reads that no longer describe what is there diff --git a/packages/git/src/deno/composition/switch.ts b/packages/git/src/deno/composition/switch.ts index 02e104141..7fe3f0fc1 100644 --- a/packages/git/src/deno/composition/switch.ts +++ b/packages/git/src/deno/composition/switch.ts @@ -9,7 +9,7 @@ * branch is created from the supplied base, or from where the checkout already * is when there is none — never from a remote asked again. * - * The retained result keeps both halves of that: what the document asked for, + * The saved result keeps both halves of that: what the document asked for, * and the commit the branch actually started from. `null` in `resolvedBase` is * itself the evidence that the branch already existed. */ @@ -46,7 +46,7 @@ export const WORKSPACE_GIT_SWITCH = "workspace_git_switch"; * The expansion makes two elements different effects and one element the same * effect across replays; the configuration fingerprint makes a document edited * to name another branch, base or checkout diverge rather than replaying the - * previous one's retained result. Durable identity is type and name, so the + * previous one's saved result. Durable identity is type and name, so the * fingerprint belongs in the name rather than beside it. */ function* describeSwitch(admitted: GitSwitchRequest): Operation { diff --git a/packages/git/src/deno/composition/worktree.ts b/packages/git/src/deno/composition/worktree.ts index c213b182a..c34892d21 100644 --- a/packages/git/src/deno/composition/worktree.ts +++ b/packages/git/src/deno/composition/worktree.ts @@ -86,7 +86,7 @@ function* retainWorktree( repository: StoredRepository, ): Operation { const checkoutPath = worktreeCheckoutPath(request.repositoryName, request.name); - // Every retained path this effect will materialize, agreed before any of them + // Every saved path this effect will materialize, agreed before any of them // is joined to a host root: the repository's own, and each sibling's. const repositoryPath = agreedRepository( repository.record, @@ -100,7 +100,7 @@ function* retainWorktree( const root = yield* host.useDirectory(); const git = gitSession(host, root); - // Every retained worktree is materialized beside the repository, not only + // Every saved worktree is materialized beside the repository, not only // the one being added. Git decides what a repository's worktrees are by // reading `.git/worktrees` and checking that each still exists; a sibling // left unmaterialized would look abandoned, and the repository this effect @@ -214,7 +214,7 @@ export function* prepareWorktreeAttachment( throw stale(subject, "checkout"); } - // Every retained sibling is not needed here: reading HEAD does not consult + // Every saved sibling is not needed here: reading HEAD does not consult // `.git/worktrees`, and this worktree's own pair is what has to resolve. const repositoryDirectory = yield* exportTree( workspace.filesystem, @@ -232,12 +232,12 @@ export function* prepareWorktreeAttachment( * * A worktree's identity is its Repository's identity and its own place inside * it, so the origin and the object format are read through the shared - * administration — and *that* is the thing worth proving: the checkout retained + * administration — and *that* is the thing worth proving: the checkout saved * for this worktree has to be a worktree of the Repository this run says it * belongs to. Git answers that directly, because `--git-common-dir` resolves * through the administration pair to the repository the worktree is linked to. * A worktree of some other repository, or a whole repository of its own sitting - * at the retained path, answers with a different directory. + * at the saved path, answers with a different directory. */ export function* worktreeDisagreement( git: GitSession, diff --git a/packages/git/src/deno/issue/github.ts b/packages/git/src/deno/issue/github.ts index e2f4e598a..aec6e5c32 100644 --- a/packages/git/src/deno/issue/github.ts +++ b/packages/git/src/deno/issue/github.ts @@ -771,7 +771,7 @@ export function parseGitHubIssueUrl(url: string): GitHubIssueLocation | undefine * the answer. What comes back is checked against what was asked for, and what * is published is the URL the document wrote — never one the response supplied. * Otherwise a service that answered with a different issue would decide what a - * document read, and the answer would be retained under the requested URL while + * document read, and the answer would be saved under the requested URL while * describing something else. The ceiling was admitted against the requested URL * too, so a response cannot reach past it by naming somewhere else. */ diff --git a/packages/git/src/deno/repositories.ts b/packages/git/src/deno/repositories.ts index 372e9c51a..9e16d11f6 100644 --- a/packages/git/src/deno/repositories.ts +++ b/packages/git/src/deno/repositories.ts @@ -8,7 +8,7 @@ * inside the Workspace filesystem, and a later Git operation moves them by * publishing a new Workspace root rather than by touching anything here. * - * The locator is retained beside the record rather than inside it. Deciding + * The locator is saved beside the record rather than inside it. Deciding * whether a reused name asks for the same repository needs the bytes; the * journal and the document need only the fingerprint, and a URL that turned out * to carry a credential is then one column rather than one history. @@ -197,8 +197,8 @@ export function insertWorktree(storage: WorkflowWorkspaceStorage, record: Worktr /** * The metadata one Workspace transaction may read and write. * - * Handed to a mutation beside the filesystem, so retained Git identity and - * retained Git bytes move together inside one transaction. It is the provider's + * Handed to a mutation beside the filesystem, so saved Git identity and + * saved Git bytes move together inside one transaction. It is the provider's * surface and not a document's: a component reaches it only by asking the * composition provider to perform an effect. */ diff --git a/packages/git/src/deno/run-composition/operations.ts b/packages/git/src/deno/run-composition/operations.ts index 916eb9ee0..4ea7b8323 100644 --- a/packages/git/src/deno/run-composition/operations.ts +++ b/packages/git/src/deno/run-composition/operations.ts @@ -11,7 +11,7 @@ * What is different is everything around them. There is no Workspace * transaction to enclose a person's own repository in, so a failure rolls back * nothing and this promises no rollback; there is no journal, so nothing is - * retained and nothing replays; and the commit lands in the checkout the person + * saved and nothing replays; and the commit lands in the checkout the person * is standing in rather than in a root the run owns. * * ## Push, and the evidence it leaves diff --git a/packages/git/src/deno/run-composition/provider.ts b/packages/git/src/deno/run-composition/provider.ts index 2b73c1c30..6177d72f8 100644 --- a/packages/git/src/deno/run-composition/provider.ts +++ b/packages/git/src/deno/run-composition/provider.ts @@ -7,7 +7,7 @@ * about lifetime and ownership. * * A workflow run's checkouts are rows in its own database, restored from - * retained history under a WorkflowRun the document must never be able to name. + * saved history under a WorkflowRun the document must never be able to name. * An ordinary run's checkouts are directories: the one the person is standing * in, and the ones under the managed root, each held for the execution by an * advisory lock and each surviving it. Nothing is journaled, nothing replays, @@ -370,7 +370,7 @@ export function* useRunComposition(options: RunCompositionOptions): Operation", ); - // Admitted where a request enters, exactly as the retained provider + // Admitted where a request enters, exactly as the saved provider // admits it: the Api is public, and a caller reaching it directly is // subject to the same boundary. return yield* liveAdd( diff --git a/packages/git/src/deno/run-composition/pull-request.ts b/packages/git/src/deno/run-composition/pull-request.ts index 25e53a348..4f9cae25c 100644 --- a/packages/git/src/deno/run-composition/pull-request.ts +++ b/packages/git/src/deno/run-composition/pull-request.ts @@ -20,7 +20,7 @@ * interruption there is no exactly-once guarantee: GitHub may have accepted a * change for which this run recorded no result, and the next run observes what * is there and decides from that. Nothing pretends otherwise, and nothing is - * retained that would let it. + * saved that would let it. */ import type { Operation } from "effection"; diff --git a/packages/git/src/deno/retained-repository/files.ts b/packages/git/src/deno/saved-repository/files.ts similarity index 97% rename from packages/git/src/deno/retained-repository/files.ts rename to packages/git/src/deno/saved-repository/files.ts index 03f6b76bd..a6fc53624 100644 --- a/packages/git/src/deno/retained-repository/files.ts +++ b/packages/git/src/deno/saved-repository/files.ts @@ -14,7 +14,7 @@ * file has changed since. * * A new mutation also takes the checkout's lease, and a new read does not. - * Being retained and being exclusive are different questions: what the lease + * Being saved and being exclusive are different questions: what the lease * answers is that one entry at a time changes a repository, so a read proceeds * while another entry holds it and never holds it itself. * @@ -56,15 +56,15 @@ import type { Around } from "@effectionx/context-api"; import { localRecordName, recordedLocalOperation, - retainedResultAhead, - retainedSelectionAhead, + savedResultAhead, + savedSelectionAhead, } from "./local.ts"; import type { ReplAmbientCheckout, ReplLocalOperation, ReplLocalRequest, ReplLocalValue, -} from "../../retained-repository/records.ts"; +} from "../../saved-repository/records.ts"; /** What the entry knows about where it is working and what it may hold. */ export interface ReplLocalRecorder { @@ -140,7 +140,7 @@ export function replFileMiddleware(recorder: ReplLocalRecorder): Partial Operation>, ): Operation> { const name = yield* localRecordName({ operation, request }); - if (yield* retainedSelectionAhead()) { + if (yield* savedSelectionAhead()) { yield* recorder.selection(); } - if (yield* retainedResultAhead(name)) { + if (yield* savedResultAhead(name)) { return yield* recordedLocalOperation({ operation, request, perform }); } @@ -164,7 +164,7 @@ function* routed( if (checkout === undefined) { return yield* perform(); } - // Only a mutation takes the lease. A read is retained like the rest, but the + // Only a mutation takes the lease. A read is saved like the rest, but the // lease is about one entry at a time *changing* a checkout, and an // observation is not a change: a read that took it would refuse while // somebody else is working, and refuse them while it is only looking. diff --git a/packages/git/src/deno/retained-repository/leases.ts b/packages/git/src/deno/saved-repository/leases.ts similarity index 100% rename from packages/git/src/deno/retained-repository/leases.ts rename to packages/git/src/deno/saved-repository/leases.ts diff --git a/packages/git/src/deno/retained-repository/local.ts b/packages/git/src/deno/saved-repository/local.ts similarity index 94% rename from packages/git/src/deno/retained-repository/local.ts rename to packages/git/src/deno/saved-repository/local.ts index baa3181d5..3ea08e08c 100644 --- a/packages/git/src/deno/retained-repository/local.ts +++ b/packages/git/src/deno/saved-repository/local.ts @@ -20,7 +20,7 @@ * Deciding whether a path is inside the checkout means resolving it, and * resolving it means touching a filesystem the saved result is not about — a * continuation whose checkout has since been deleted would fail at the question - * rather than answer from what it already knows. So the retained cursor is read + * rather than answer from what it already knows. So the saved cursor is read * first, without consuming anything: a position holding this operation's own * record is answered from the record, and only an operation with nothing saved * resolves, routes, leases and performs. @@ -53,13 +53,13 @@ import { REPL_REPOSITORY_LOCAL, REPL_REPOSITORY_SELECTION, retainableRefusal, -} from "../../retained-repository/records.ts"; +} from "../../saved-repository/records.ts"; import type { ReplAmbientCheckout, ReplLocalOperation, ReplLocalRequest, ReplLocalValue, -} from "../../retained-repository/records.ts"; +} from "../../saved-repository/records.ts"; /** * A saved record standing where this operation belongs that it cannot read. @@ -101,14 +101,14 @@ export function* localRecordName(operation: { } /** - * Whether the next retained position in this coroutine holds this record. + * Whether the next saved position in this coroutine holds this record. * * Reads the description alone and consumes nothing. A `true` says a saved * result is probably standing here, which is reason enough to answer from the * journal instead of resolving a path; the matcher below decides whether it * really is. */ -export function* retainedRecordAhead(type: string, name?: string): Operation { +export function* savedRecordAhead(type: string, name?: string): Operation { const context = yield* DurableContext.expect(); const entry = context.replayIndex.peekYield(context.coroutineId); if (entry === undefined || entry.description.type !== type) { @@ -131,13 +131,13 @@ export function* currentCoroutine(): Operation { } /** Whether this entry's saved ambient routing stands at the cursor. */ -export function retainedSelectionAhead(): Operation { - return retainedRecordAhead(REPL_REPOSITORY_SELECTION); +export function savedSelectionAhead(): Operation { + return savedRecordAhead(REPL_REPOSITORY_SELECTION); } /** Whether this operation's own saved result stands at the cursor. */ -export function retainedResultAhead(name: string): Operation { - return retainedRecordAhead(REPL_REPOSITORY_LOCAL, name); +export function savedResultAhead(name: string): Operation { + return savedRecordAhead(REPL_REPOSITORY_LOCAL, name); } /** diff --git a/packages/git/src/deno/retained-repository/provider.ts b/packages/git/src/deno/saved-repository/provider.ts similarity index 98% rename from packages/git/src/deno/retained-repository/provider.ts rename to packages/git/src/deno/saved-repository/provider.ts index 880f56d7b..bb2e99d2b 100644 --- a/packages/git/src/deno/retained-repository/provider.ts +++ b/packages/git/src/deno/saved-repository/provider.ts @@ -43,7 +43,7 @@ import type { GitSession } from "../composition/git.ts"; import { discoverAmbientCheckout } from "../run-composition/ambient.ts"; import { lazily } from "../run-composition/lazy.ts"; import { useCommonDirectoryLease } from "./leases.ts"; -import type { ReplAmbientCheckout } from "../../retained-repository/records.ts"; +import type { ReplAmbientCheckout } from "../../saved-repository/records.ts"; import { currentCoroutine, selectedCheckout } from "./local.ts"; import { replFileMiddleware } from "./files.ts"; import type { ReplLocalRecorder } from "./files.ts"; diff --git a/packages/git/src/git-host/effect-type.ts b/packages/git/src/git-host/effect-type.ts index 9240020c6..4949f26cd 100644 --- a/packages/git/src/git-host/effect-type.ts +++ b/packages/git/src/git-host/effect-type.ts @@ -2,7 +2,7 @@ * The durable type every external Git-host effect is journaled under. * * Its own module because two readers need it without needing the reconciliation - * that writes it: forkability classifies a retained event by type, and the + * that writes it: forkability classifies a saved event by type, and the * replay-identity admission recognizes one in a history. Importing the shared * effect for a string would pull the provider surface into both. */ diff --git a/packages/git/src/git-host/effect.ts b/packages/git/src/git-host/effect.ts index 83b0b7d73..a1c8d5f2b 100644 --- a/packages/git/src/git-host/effect.ts +++ b/packages/git/src/git-host/effect.ts @@ -11,7 +11,7 @@ * * Run and expansion identify the effect; the complete detached request's digest * names the durable operation so changed inputs cannot consume an earlier answer. - * Retained identities and abandoned-history enforcement remain checked on the live + * Saved identities and abandoned-history enforcement remain checked on the live * path before provider work. * * {@link GitHost} exposes one frozen, one-use routing request. Public middleware @@ -31,7 +31,7 @@ import { createApi } from "@effectionx/context-api"; import { createDurableOperation, serializeError } from "@executablemd/durable-streams"; import type { - AbandonedRetainedHistory, + AbandonedSavedHistory, ActivateDurabilityFailure, EffectDescription, JournalProvenance, @@ -44,11 +44,11 @@ import { Err, ensure, Ok, scoped } from "effection"; import type { Operation, Result } from "effection"; import { getExpansion, sourceDescription } from "@executablemd/core"; import { getWorkflowRun } from "@executablemd/workflow"; -import { retainedGitHostIdentitiesHere } from "../identities.ts"; +import { savedGitHostIdentitiesHere } from "../identities.ts"; import { GIT_HOST_EFFECT } from "./effect-type.ts"; -import { claimRetainedGitHostIdentity, exhaustRetainedGitHostIdentities } from "./identities.ts"; +import { claimSavedGitHostIdentity, exhaustSavedGitHostIdentities } from "./identities.ts"; -/** A request named by a retained record, and how to retire the rest of them. */ +/** A request named by a saved record, and how to retire the rest of them. */ interface BorrowedIdentity { exhaust(): void; } @@ -479,22 +479,22 @@ function gitHostCoordinator( publish: (result: DurableResult) => Operation, activateFailure: ActivateDurabilityFailure, _journalProvenance: JournalProvenance | undefined, - abandoned: AbandonedRetainedHistory, + abandoned: AbandonedSavedHistory, ): Operation { // Reached only live, and never on replay. // // Two ways a live attempt here is not this run's to make, and neither is // decided by anything a document supplies. `steppedOver` is the engine's - // own account of the coroutine: it walked away from retained history it + // own account of the coroutine: it walked away from saved history it // never consumed, so records still ahead of it describe work that already // happened at a service this journal does not enclose — and doing any of // it again is exactly what this boundary exists to prevent. It stays true // for every operation after the one that diverged, which is the whole // point: guarding only the diverging position leaves every position after - // it open. `borrowed` says the request was named by a retained record, + // it open. `borrowed` says the request was named by a saved record, // which cannot be a live request either. // - // The first is the load-bearing one: it holds whether or not the retained + // The first is the load-bearing one: it holds whether or not the saved // identities reached this copy, so erasing or replacing them cannot buy a // live effect. Nothing has been executed yet, so the Git host is never // reached and nothing is published. @@ -502,7 +502,7 @@ function gitHostCoordinator( borrowed?.exhaust(); throw activateFailure( new GitHostProviderError( - "this run walked away from retained history it never consumed, so a Git-host " + + "this run walked away from saved history it never consumed, so a Git-host " + "effect here would repeat work that already happened outside this journal, or " + "perform under a run this execution is not. Nothing was asked of the Git host.", ), @@ -593,21 +593,19 @@ export function* reconcileGitHostEffect( const expansion = yield* getExpansion(); // A position this run already retains a record at is named by the identity // that record holds; everywhere else by this run's own. - // The live form first, because what a retained record has to match is the + // The live form first, because what a saved record has to match is the // request being made — and the run id is the one member being decided. const live = completeRequest(run.runId, expansion.id, request); - const held = yield* retainedGitHostIdentitiesHere(); - const retainedIdentity = claimRetainedGitHostIdentity(held, live); + const held = yield* savedGitHostIdentitiesHere(); + const savedIdentity = claimSavedGitHostIdentity(held, live); const complete = - retainedIdentity === undefined - ? live - : completeRequest(retainedIdentity, expansion.id, request); - // Carried into the live path so a name that came from a retained record + savedIdentity === undefined ? live : completeRequest(savedIdentity, expansion.id, request); + // Carried into the live path so a name that came from a saved record // cannot be the one a live attempt performs under. const borrowed: BorrowedIdentity | undefined = - retainedIdentity === undefined + savedIdentity === undefined ? undefined - : { exhaust: () => exhaustRetainedGitHostIdentities(held) }; + : { exhaust: () => exhaustSavedGitHostIdentities(held) }; const description: EffectDescription = { type: GIT_HOST_EFFECT, name: yield* gitHostRequestFingerprint(complete), @@ -617,14 +615,14 @@ export function* reconcileGitHostEffect( ...sourceDescription(expansion.position), }; - let retained: unknown; + let saved: unknown; try { - retained = yield* attempt(description, complete, borrowed); + saved = yield* attempt(description, complete, borrowed); } catch (error) { throw gitHostFailure(error); } - const record = parseGitHostReconciliationRecord(retained); + const record = parseGitHostReconciliationRecord(saved); if (record === undefined) { throw new GitHostProtocolError( "the journal holds a value that does not describe a Git-host reconciliation record", diff --git a/packages/git/src/git-host/identities.ts b/packages/git/src/git-host/identities.ts index 266c2d227..4f1948b70 100644 --- a/packages/git/src/git-host/identities.ts +++ b/packages/git/src/git-host/identities.ts @@ -15,11 +15,11 @@ * chosen that way would reach a live observation, a live performance and the * journal. * - * So the table is built in one place — the retained-run installation's + * So the table is built in one place — the saved-run installation's * admission, inside core's own trusted journal read, from the snapshot core * admitted — and published beside the run itself, where every physical copy of * this package reads it. A second copy loaded from disk reconciles through the - * same stable operation name (Tier DLG), and it must see the same retained + * same stable operation name (Tier DLG), and it must see the same saved * identities: a fork whose Git-host history replayed for one copy and diverged * for another would accept different history depending on which module object * asked. @@ -27,13 +27,13 @@ * What makes an answer safe is not where it came from but what happens to a * wrong one. On replay the shared reconciliation holds the record it consumed * to the request being made, so an identity that is not that record's own is a - * refusal. And a request named by a retained record that reaches live execution + * refusal. And a request named by a saved record that reaches live execution * performs nothing at all. A substituted answer can therefore deny this effect; * it cannot make it observe, perform or journal under another run. * * ## One record, one position, in order * - * A retained identity belongs to the exact retained event it came from, and to + * A saved identity belongs to the exact saved event it came from, and to * the position that event occupies. `reconcileGitHostEffect()` is a public * surface, so one expansion may reach it more than once, and a fork whose * inherited prefix ends after the first of those calls must not lend the @@ -50,7 +50,7 @@ * * Falling through to live is final. Once a call runs live, replay has left the * inherited prefix, and nothing after it may borrow: the reconciliation reports - * that back through {@link exhaustRetainedGitHostIdentities}, so a borrowed name + * that back through {@link exhaustSavedGitHostIdentities}, so a borrowed name * that reaches the live path takes the rest of the prefix out of use with it. * * A caller holding a run object cannot enroll it, and a caller holding a @@ -67,8 +67,8 @@ import { GIT_HOST_EFFECT } from "./effect-type.ts"; import { parseGitHostReconciliationRecord } from "./records.ts"; import type { CompleteGitHostEffectRequest } from "./records.ts"; -/** One retained record's identity, and what it was a record of. */ -export interface RetainedIdentity { +/** One saved record's identity, and what it was a record of. */ +export interface SavedIdentity { readonly runId: string; /** The request it answers for, with the run it was written under left out. */ readonly asked: string; @@ -78,12 +78,12 @@ export interface RetainedIdentity { /** * The identities one admitted history holds, in the order it holds them. * - * Built by the retained-run installation's admission, from the snapshot + * Built by the saved-run installation's admission, from the snapshot * canonical core admitted, and published where every physical copy of this * package reads it. */ -export function retainedGitHostIdentities(retained: readonly DurableEvent[]): RetainedIdentity[] { - return identitiesIn(retained); +export function savedGitHostIdentities(saved: readonly DurableEvent[]): SavedIdentity[] { + return identitiesIn(saved); } /** @@ -92,20 +92,20 @@ export function retainedGitHostIdentities(retained: readonly DurableEvent[]): Re * Called when a call has run live: replay has left the prefix, and a later * record must not be borrowed by whatever happens next. */ -export function exhaustRetainedGitHostIdentities(held: RetainedIdentity[] | undefined): void { +export function exhaustSavedGitHostIdentities(held: SavedIdentity[] | undefined): void { if (held !== undefined) { exhaust(held); } } /** - * Take the identity of the retained record at the next unconsumed position, + * Take the identity of the saved record at the next unconsumed position, * when that record asks exactly what this request asks. * * Nothing is the ordinary answer, and it is what every live position gives. */ -export function claimRetainedGitHostIdentity( - held: RetainedIdentity[] | undefined, +export function claimSavedGitHostIdentity( + held: SavedIdentity[] | undefined, request: CompleteGitHostEffectRequest, ): string | undefined { if (held === undefined) { @@ -130,7 +130,7 @@ export function claimRetainedGitHostIdentity( * expansion stays in, so a record never answers for a position somewhere else * in the document. */ -function exhaust(held: RetainedIdentity[]): void { +function exhaust(held: SavedIdentity[]): void { for (const identity of held) { identity.claimed = true; } @@ -153,9 +153,9 @@ function asked(request: CompleteGitHostEffectRequest): string { * outcome is the durable operation's failed result — and there is nothing there * to name an identity with. */ -function identitiesIn(retained: readonly DurableEvent[]): RetainedIdentity[] { - const identities: RetainedIdentity[] = []; - for (const event of retained) { +function identitiesIn(saved: readonly DurableEvent[]): SavedIdentity[] { + const identities: SavedIdentity[] = []; + for (const event of saved) { const request = requestOf(event); if (request === undefined) { continue; diff --git a/packages/git/src/git-host/records.ts b/packages/git/src/git-host/records.ts index d0cd3c2b6..3c4fc5a84 100644 --- a/packages/git/src/git-host/records.ts +++ b/packages/git/src/git-host/records.ts @@ -86,7 +86,7 @@ export type GitHostDecision = "adopted" | "performed"; * The complete successful record one external effect retains. * * The request is repeated inside the record on purpose. A replayed record is - * read back and compared with the request being made now, so a retained result + * read back and compared with the request being made now, so a saved result * is consumed by the effect that produced it rather than by whatever arrives at * the same journal position. */ @@ -378,7 +378,7 @@ export function sameGitHostEffectRequest( * * A digest of the canonical encoding, so the durable operation's own identity * moves when the kind, the inputs or the natural key move. Without it a changed - * request would arrive at a matching `type` and name and consume the retained + * request would arrive at a matching `type` and name and consume the saved * result of a different question. * * The platform's `crypto`, adapted with `until`: this is shared code, and it diff --git a/packages/git/src/identities.ts b/packages/git/src/identities.ts index 10eb9a878..dc4d80861 100644 --- a/packages/git/src/identities.ts +++ b/packages/git/src/identities.ts @@ -6,9 +6,9 @@ * An Issue effect is a different request shape reconciled through a different * boundary, and what it needs from the history is the same association. * - * Both are read out of the retained snapshot by a journal admission, which + * Both are read out of the saved snapshot by a journal admission, which * canonical core applies inside the execution's own journal read — on the exact - * frozen events every later phase consumes, before any middleware, any retained + * frozen events every later phase consumes, before any middleware, any saved * Yield reaching execution, any authored work and any append. The value the * admission installs is therefore this execution's, computed from this * execution's history, and a second execution under the same Plugin computes @@ -24,13 +24,13 @@ import { createContext } from "effection"; import type { Context, Operation } from "effection"; import type { DurableEvent } from "@executablemd/durable-streams"; import type { JournalAdmission } from "@executablemd/core/host"; -import { retainedGitHostIdentities } from "./git-host/identities.ts"; -import type { RetainedIdentity } from "./git-host/identities.ts"; -import { retainedIssueIdentities } from "./issue/identities.ts"; -import type { RetainedIssueIdentity } from "./issue/identities.ts"; +import { savedGitHostIdentities } from "./git-host/identities.ts"; +import type { SavedIdentity } from "./git-host/identities.ts"; +import { savedIssueIdentities } from "./issue/identities.ts"; +import type { SavedIssueIdentity } from "./issue/identities.ts"; /** - * Where this execution's retained Git-host identities are kept. + * Where this execution's saved Git-host identities are kept. * * A stable, namespaced name and a plain value, so a second physical copy of * this package reads the same binding through its own descriptor and finds the @@ -38,26 +38,24 @@ import type { RetainedIssueIdentity } from "./issue/identities.ts"; * descendant may bind the name for its own descendants — which is why nothing * durable depends on what it holds. */ -const RetainedGitHostIdentities: Context = createContext< - readonly RetainedIdentity[] | undefined ->("executablemd.git.git-host.retained-identities", undefined); +const SavedGitHostIdentities: Context = createContext< + readonly SavedIdentity[] | undefined +>("executablemd.git.git-host.saved-identities", undefined); -/** Where this execution's retained Issue identities are kept, on the same terms. */ -const RetainedIssueIdentities: Context = - createContext( - "executablemd.git.issue.retained-identities", - undefined, - ); +/** Where this execution's saved Issue identities are kept, on the same terms. */ +const SavedIssueIdentities: Context = createContext< + readonly SavedIssueIdentity[] | undefined +>("executablemd.git.issue.saved-identities", undefined); /** The Git-host identities this execution's admitted history holds. */ -export function* retainedGitHostIdentitiesHere(): Operation { - const held = yield* RetainedGitHostIdentities.get(); +export function* savedGitHostIdentitiesHere(): Operation { + const held = yield* SavedGitHostIdentities.get(); return held === undefined ? undefined : [...held]; } /** The Issue identities this execution's admitted history holds. */ -export function* retainedIssueIdentitiesHere(): Operation { - const held = yield* RetainedIssueIdentities.get(); +export function* savedIssueIdentitiesHere(): Operation { + const held = yield* SavedIssueIdentities.get(); return held === undefined ? undefined : [...held]; } @@ -70,14 +68,14 @@ export function* retainedIssueIdentitiesHere(): Operation { - yield* RetainedGitHostIdentities.set(Object.freeze(retainedGitHostIdentities(retained))); + return function* (saved: readonly DurableEvent[]): Operation { + yield* SavedGitHostIdentities.set(Object.freeze(savedGitHostIdentities(saved))); }; } /** Publish the Issue identities this execution's history holds, on the same terms. */ export function issueIdentityAdmission(): JournalAdmission { - return function* (retained: readonly DurableEvent[]): Operation { - yield* RetainedIssueIdentities.set(Object.freeze(retainedIssueIdentities(retained))); + return function* (saved: readonly DurableEvent[]): Operation { + yield* SavedIssueIdentities.set(Object.freeze(savedIssueIdentities(saved))); }; } diff --git a/packages/git/src/issue/effect-type.ts b/packages/git/src/issue/effect-type.ts index ae0533497..98e992775 100644 --- a/packages/git/src/issue/effect-type.ts +++ b/packages/git/src/issue/effect-type.ts @@ -1,7 +1,7 @@ /** * The durable type every external Issue effect is journaled under. * - * Its own module for the reason the Git-host one has its own: the retained + * Its own module for the reason the Git-host one has its own: the saved * identity admission classifies an event by type without needing the * reconciliation that writes it, and importing the effect for a string would * pull the provider surface in with it. diff --git a/packages/git/src/issue/effect.ts b/packages/git/src/issue/effect.ts index e042d9af2..a0ec146ae 100644 --- a/packages/git/src/issue/effect.ts +++ b/packages/git/src/issue/effect.ts @@ -11,7 +11,7 @@ * * Both journal as `issue_effect`, and the request carries which one it is. That * discriminator is in the durable name and, for an upsert, in the idempotency - * key — so a read can never arrive at an upsert's retained result, and two + * key — so a read can never arrive at an upsert's saved result, and two * reads of different issues at one position are two different questions. * * ## What each guarantees, and what it does not @@ -22,7 +22,7 @@ * reading a live object means. * * An upsert guarantees that the call happens at most once per position and that - * a replay returns the retained URL. It does not guarantee that the *service* + * a replay returns the saved URL. It does not guarantee that the *service* * was touched once: that is the provider's, and the idempotency key is what this * side hands over so the provider can make it true. An attempt interrupted * after the service accepted it leaves no journal entry, and the next attempt @@ -45,8 +45,8 @@ import { } from "@executablemd/durable-streams"; import { getExpansion, sourceDescription } from "@executablemd/core"; import { getWorkflowRun } from "@executablemd/workflow"; -import { retainedIssueIdentitiesHere } from "../identities.ts"; -import { claimRetainedIssueIdentity, exhaustRetainedIssueIdentities } from "./identities.ts"; +import { savedIssueIdentitiesHere } from "../identities.ts"; +import { claimSavedIssueIdentity, exhaustSavedIssueIdentities } from "./identities.ts"; import { ISSUE_EFFECT } from "./effect-type.ts"; import { IssueApi } from "./api.ts"; import { IssueOperations } from "./operations.ts"; @@ -98,27 +98,24 @@ export function* readIssue(request: IssueReadEffectRequest): Operation { - const answered = yield* IssueApi.operations.read(complete.url, { - ...(complete.provider === null ? {} : { provider: complete.provider }), - }); - // Validated before it is retained. A provider that answered with more - // than the shared fields answered with something this boundary will not - // carry, and reading it anyway is how a provider's own shape reaches a - // document. - const details = parseIssueDetails(answered); - if (details === undefined) { - throw new IssueProtocolError( - "the issue provider answered a read with something that is not an issue's shared " + - "fields, so nothing was retained", - ); - } - return issueDetailsJson(details); - }, - ); - const details = parseIssueDetails(retained); + const saved: unknown = yield* attempt(yield* describe(complete), function* (): Operation { + const answered = yield* IssueApi.operations.read(complete.url, { + ...(complete.provider === null ? {} : { provider: complete.provider }), + }); + // Validated before it is saved. A provider that answered with more + // than the shared fields answered with something this boundary will not + // carry, and reading it anyway is how a provider's own shape reaches a + // document. + const details = parseIssueDetails(answered); + if (details === undefined) { + throw new IssueProtocolError( + "the issue provider answered a read with something that is not an issue's shared " + + "fields, so nothing was saved", + ); + } + return issueDetailsJson(details); + }); + const details = parseIssueDetails(saved); if (details === undefined) { throw new IssueProtocolError( "the journal holds a value that does not describe an issue's shared fields", @@ -131,7 +128,7 @@ export function* readIssue(request: IssueReadEffectRequest): Operation { @@ -152,43 +149,40 @@ export function* upsertIssue(request: IssueUpsertEffectRequest): Operation { - if (borrowed !== undefined) { - // The request was named by a retained record, which cannot be a live - // request: performing under a run this execution is not would repeat - // work that already happened at a service this journal does not - // enclose. - exhaustRetainedIssueIdentities(held); - throw new IssueProtocolError( - "this request is named by retained history it never consumed, so creating an issue " + - "here would repeat work that already happened outside this journal", - ); - } - const answered = yield* IssueApi.operations.upsert(complete.issue, { - url: complete.target, - ...(complete.provider === null ? {} : { provider: complete.provider }), - idempotencyKey: issueIdempotencyKey(complete.identity, "upsert", complete.target), - }); - const record = parseIssueRecord(answered); - if (record === undefined) { - throw new IssueProtocolError( - "the issue provider answered an upsert with something that is not a URL, so nothing " + - "was retained", - ); - } - return issueRecordJson(record); - }, - ); - const record = parseIssueRecord(retained); + const saved: unknown = yield* attempt(yield* describe(complete), function* (): Operation { + if (borrowed !== undefined) { + // The request was named by a saved record, which cannot be a live + // request: performing under a run this execution is not would repeat + // work that already happened at a service this journal does not + // enclose. + exhaustSavedIssueIdentities(held); + throw new IssueProtocolError( + "this request is named by saved history it never consumed, so creating an issue " + + "here would repeat work that already happened outside this journal", + ); + } + const answered = yield* IssueApi.operations.upsert(complete.issue, { + url: complete.target, + ...(complete.provider === null ? {} : { provider: complete.provider }), + idempotencyKey: issueIdempotencyKey(complete.identity, "upsert", complete.target), + }); + const record = parseIssueRecord(answered); + if (record === undefined) { + throw new IssueProtocolError( + "the issue provider answered an upsert with something that is not a URL, so nothing " + + "was saved", + ); + } + return issueRecordJson(record); + }); + const record = parseIssueRecord(saved); if (record === undefined) { throw new IssueProtocolError( "the journal holds a value that does not describe an issue reference", @@ -208,7 +202,7 @@ function* describe(request: IssueRequest): Operation { return { type: ISSUE_EFFECT, name: yield* issueRequestFingerprint(request), - // Carried in the description so a retained history describes what it asked + // Carried in the description so a saved history describes what it asked // for. The identity table reads it back to recognize a record a fork // inherited, and it holds only what a document wrote. request: issueRequestJson(request), @@ -229,7 +223,7 @@ function* attempt( export { issueRequestJson }; /** - * Install the retained Issue lifecycle for the current scope and below. + * Install the saved Issue lifecycle for the current scope and below. * * What a workflow run adds to the transport underneath is exactly the envelope * above: one durable effect per operation, named by this run and this @@ -237,7 +231,7 @@ export { issueRequestJson }; * `IssueApi` middleware still owns which service is reached and what a * credential may see. */ -export function useRetainedIssueOperations(): Operation { +export function useSavedIssueOperations(): Operation { return IssueOperations.around( { read: ([invocation]: [IssueReadInvocation]) => readIssue(invocation), diff --git a/packages/git/src/issue/errors.ts b/packages/git/src/issue/errors.ts index 6a3666bb6..ec3508796 100644 --- a/packages/git/src/issue/errors.ts +++ b/packages/git/src/issue/errors.ts @@ -61,7 +61,7 @@ export class IssueProtocolError extends Error { constructor(condition: string) { super( - `${condition}. The answer is refused rather than retained: a value this boundary ` + + `${condition}. The answer is refused rather than saved: a value this boundary ` + "cannot read is not an outcome, and its content is withheld.", ); } diff --git a/packages/git/src/issue/identities.ts b/packages/git/src/issue/identities.ts index ebe953b96..cabc23021 100644 --- a/packages/git/src/issue/identities.ts +++ b/packages/git/src/issue/identities.ts @@ -22,11 +22,11 @@ * * The reasoning about *why* the table is built where it is belongs to * `git-host/identities.ts` and holds identically here: every obvious way of - * carrying the answer is replaceable, so it is built once in the retained-run + * carrying the answer is replaceable, so it is built once in the saved-run * installation's admission, inside core's own trusted journal read, and * published beside the run where every physical copy of this package reads it. * What makes a wrong answer safe is what happens to it: on replay the record - * consumed is held to the request being made, and a request named by a retained + * consumed is held to the request being made, and a request named by a saved * record that reaches live execution performs nothing at all. */ @@ -36,8 +36,8 @@ import { ISSUE_EFFECT } from "./effect-type.ts"; import { parseIssueRequest } from "./records.ts"; import type { IssueRequest } from "./records.ts"; -/** One retained record's identity, and what it was a record of. */ -export interface RetainedIssueIdentity { +/** One saved record's identity, and what it was a record of. */ +export interface SavedIssueIdentity { readonly runId: string; /** The request it answers for, with the run it was written under left out. */ readonly asked: string; @@ -47,15 +47,13 @@ export interface RetainedIssueIdentity { /** * The identities one admitted history holds, in the order it holds them. * - * Built by the retained-run installation's admission, from the snapshot + * Built by the saved-run installation's admission, from the snapshot * canonical core admitted, and published where every physical copy of this * package reads it. */ -export function retainedIssueIdentities( - retained: readonly DurableEvent[], -): RetainedIssueIdentity[] { - const identities: RetainedIssueIdentity[] = []; - for (const event of retained) { +export function savedIssueIdentities(saved: readonly DurableEvent[]): SavedIssueIdentity[] { + const identities: SavedIssueIdentity[] = []; + for (const event of saved) { const request = requestOf(event); if (request === undefined) { continue; @@ -71,20 +69,20 @@ export function retainedIssueIdentities( * Called when a call has run live: replay has left the prefix, and a later * record must not be borrowed by whatever happens next. */ -export function exhaustRetainedIssueIdentities(held: RetainedIssueIdentity[] | undefined): void { +export function exhaustSavedIssueIdentities(held: SavedIssueIdentity[] | undefined): void { if (held !== undefined) { exhaust(held); } } /** - * Take the identity of the retained record at the next unconsumed position, + * Take the identity of the saved record at the next unconsumed position, * when that record asks exactly what this request asks. * * Nothing is the ordinary answer, and it is what every live position gives. */ -export function claimRetainedIssueIdentity( - held: RetainedIssueIdentity[] | undefined, +export function claimSavedIssueIdentity( + held: SavedIssueIdentity[] | undefined, request: IssueRequest, ): string | undefined { if (held === undefined) { @@ -102,7 +100,7 @@ export function claimRetainedIssueIdentity( return next.runId; } -function exhaust(held: RetainedIssueIdentity[]): void { +function exhaust(held: SavedIssueIdentity[]): void { for (const identity of held) { identity.claimed = true; } diff --git a/packages/git/src/issue/operations.ts b/packages/git/src/issue/operations.ts index 0781be6b1..4befa7beb 100644 --- a/packages/git/src/issue/operations.ts +++ b/packages/git/src/issue/operations.ts @@ -5,7 +5,7 @@ * `IssueApi` is the transport surface — GitHub's middleware recognizes its own * URLs, holds a target to the host's ceiling, and normalizes what comes back. * This is the layer above it, and what it owns is *lifecycle*: whether one - * question is asked once and retained, or asked once per run. + * question is asked once and saved, or asked once per run. * * The two profiles answer that differently, which is why the seam exists. A * workflow run wraps each operation in a durable effect keyed by its WorkflowRun @@ -52,7 +52,7 @@ export class IssueOperationsProviderError extends Error { constructor(operation: string) { super( `no Issue provider is installed, so ${operation} cannot answer. The Deno and compiled ` + - "`xmd run` entrypoints install the ordinary one; a workflow host installs the retained " + + "`xmd run` entrypoints install the ordinary one; a workflow host installs the saved " + "one for a live or partial execution.", ); } diff --git a/packages/git/src/issue/records.ts b/packages/git/src/issue/records.ts index acffa996d..b24a557fb 100644 --- a/packages/git/src/issue/records.ts +++ b/packages/git/src/issue/records.ts @@ -19,7 +19,7 @@ * by, and it covers every member: the canonical target, the discriminator, the * title, the description, the normalized tags and the assignee. A changed * request therefore diverges at the durable position rather than consuming the - * result retained for a different question. + * result saved for a different question. * * ## Tags are a set * @@ -33,7 +33,7 @@ * ## Absence has one spelling * * An absent assignee is `null` from the moment the component reads its props, - * in the request, and in the retained record. `undefined` is not a JSON value, + * in the request, and in the saved record. `undefined` is not a JSON value, * and a member that is sometimes missing is a second shape rather than a value. */ @@ -79,7 +79,7 @@ export interface IssueUpsertRequest { * The complete durable request one `` makes. * * Discriminated, because the two are different questions and a name that could - * be either would let a read consume an upsert's retained result. + * be either would let a read consume an upsert's saved result. */ export type IssueRequest = IssueReadRequest | IssueUpsertRequest; @@ -270,7 +270,7 @@ export function parseIssueInput(value: unknown): IssueInput | undefined { ) { return undefined; } - // Read back as a set. A retained value whose tags are out of order or + // Read back as a set. A saved value whose tags are out of order or // repeated is not one this boundary wrote, and reading it as though it were // would let two spellings of one request answer for each other. const written = read["tags"]; @@ -400,7 +400,7 @@ export function issueIdempotencyKey( * A digest of the canonical encoding, so the durable operation's own identity * moves when the target, the discriminator or any authored field moves. * Without it a changed request would arrive at a matching type and name and - * consume the result retained at the same journal position. + * consume the result saved at the same journal position. */ export function* issueRequestFingerprint(request: IssueRequest): Operation { const encoded = new TextEncoder().encode(canonicalJson(issueRequestJson(request))); diff --git a/packages/git/src/plugin.ts b/packages/git/src/plugin.ts index 85ce6bb56..1705e5127 100644 --- a/packages/git/src/plugin.ts +++ b/packages/git/src/plugin.ts @@ -23,7 +23,7 @@ * installed alongside them because canonical core applies an admission inside * each execution's own journal read: a host that installs this Plugin once and * runs two documents gets two journal reads, so each execution derives its - * identities from its own retained history and neither can read the other's. + * identities from its own saved history and neither can read the other's. */ import type { Operation } from "effection"; @@ -34,7 +34,7 @@ import type { PluginInstallation, } from "@executablemd/core/api"; import { useCompositionComponents } from "./composition/installation.ts"; -import { admitReplRepositoryHistory } from "./retained-repository/admission.ts"; +import { admitReplRepositoryHistory } from "./saved-repository/admission.ts"; import { gitHostIdentityAdmission, issueIdentityAdmission } from "./identities.ts"; /** @@ -201,12 +201,12 @@ export const gitPlugin: Plugin = Plugin({ admissions: [ gitHostIdentityAdmission(), issueIdentityAdmission(), - // The REPL's own: what an entry retained about the checkout it was + // The REPL's own: what an entry saved about the checkout it was // standing in has to be readable before any of it is replayed. It // belongs to this contribution because an admission is applied inside // each execution's journal read, and this is the trusted path that // already declares for the command. It holds no native state and names - // no runtime: a Node session that retained nothing has nothing here to + // no runtime: a Node session that saved nothing has nothing here to // refuse. ...(request.command === "repl" ? [replHistoryAdmission()] : []), ], @@ -222,7 +222,7 @@ export const gitPlugin: Plugin = Plugin({ * belongs to is the stream segment's question, not this one's. */ function replHistoryAdmission(): JournalAdmission { - return (retained) => admitReplRepositoryHistory(retained); + return (saved) => admitReplRepositoryHistory(saved); } export default gitPlugin; diff --git a/packages/git/src/retained-repository/admission.ts b/packages/git/src/saved-repository/admission.ts similarity index 91% rename from packages/git/src/retained-repository/admission.ts rename to packages/git/src/saved-repository/admission.ts index 0f94be0af..7858a2bde 100644 --- a/packages/git/src/retained-repository/admission.ts +++ b/packages/git/src/saved-repository/admission.ts @@ -1,5 +1,5 @@ /** - * Whether one entry's retained history may be continued at all. + * Whether one entry's saved history may be continued at all. * * Two questions, and only two. **Integrity**: every record of this feature's * own types parses and is named by the request it carries. **Nothing else**: no @@ -67,15 +67,15 @@ export class ReplRepositoryHistoryError extends StaleInputError { } /** - * Hold this entry's retained history to what this host can read, or refuse. + * Hold this entry's saved history to what this host can read, or refuse. * - * `retained` is the exact snapshot canonical execution admitted, which for an + * `saved` is the exact snapshot canonical execution admitted, which for an * entry is its own segment. Records of other features and of other durable * types are not this reader's business and are passed over; a record of *this* * feature's type that does not parse is, and refuses. */ -export function* admitReplRepositoryHistory(retained: readonly DurableEvent[]): Operation { - for (const event of retained) { +export function* admitReplRepositoryHistory(saved: readonly DurableEvent[]): Operation { + for (const event of saved) { if (event.type !== "yield") { continue; } @@ -142,7 +142,7 @@ function malformed(type: string): ReplRepositoryHistoryError { function changed(type: string): ReplRepositoryHistoryError { return refusal( "request-fingerprint", - `a retained ${type} carries a fingerprint that is not the digest of the request beside it, ` + + `a saved ${type} carries a fingerprint that is not the digest of the request beside it, ` + "so the request was changed after the work was saved and this history cannot say which of " + "the two the saved work belongs to.", ); @@ -151,7 +151,7 @@ function changed(type: string): ReplRepositoryHistoryError { function misnamed(type: string): ReplRepositoryHistoryError { return refusal( "request-fingerprint", - `a retained ${type} is journaled under a name that is not the name its own request ` + + `a saved ${type} is journaled under a name that is not the name its own request ` + "produces, so the record and the operation it claims to be do not match.", ); } diff --git a/packages/git/src/retained-repository/records.ts b/packages/git/src/saved-repository/records.ts similarity index 98% rename from packages/git/src/retained-repository/records.ts rename to packages/git/src/saved-repository/records.ts index 891f68257..40a867d34 100644 --- a/packages/git/src/retained-repository/records.ts +++ b/packages/git/src/saved-repository/records.ts @@ -11,7 +11,7 @@ * call outside the checkout is ordinary filesystem behavior and is recorded by * nothing here, because giving it a record would place it in a checkout it is not in. * Globs, path checks and temporary directories are recorded nowhere: a read is - * retained because a document binds its text and a continuation has to offer + * saved because a document binds its text and a continuation has to offer * the same binding, and those three bind nothing. * * ## Identity is the request, and position is the journal's @@ -39,7 +39,7 @@ * provider reported, so a continuation reconstructs the same `Result` the live * call returned. * - * Nothing else is retained — no native exception, no credential, no directory + * Nothing else is saved — no native exception, no credential, no directory * listing, no bytes read from another file and no handle. */ @@ -365,7 +365,7 @@ function parseRefusal(value: unknown): Result | undefined { function failureData(error: Error): FilesErrorData { const data = failureOf(error); if (data === undefined) { - throw new TypeError("a retained refusal carries no ordinary Files failure data"); + throw new TypeError("a saved refusal carries no ordinary Files failure data"); } return data; } diff --git a/packages/git/tests/ambient-authentication.test.ts b/packages/git/tests/ambient-authentication.test.ts index 770941073..240685f48 100644 --- a/packages/git/tests/ambient-authentication.test.ts +++ b/packages/git/tests/ambient-authentication.test.ts @@ -82,7 +82,7 @@ import { inCheckout, TEST_HELPER, raised, - retainedRepositories, + savedRepositories, runDocument, runWorkflowDocument, subcommands, @@ -178,7 +178,7 @@ function* present(path: string): Operation { } function* checkoutPath(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { throw new Error("the run retains no Repository"); } @@ -233,7 +233,7 @@ function published(locator: string, ...extra: string[]): string { ); } -/** Append configuration to the retained checkout mid-document. */ +/** Append configuration to the saved checkout mid-document. */ function plant(database: WorkflowRunDatabase, configuration: string): ComponentRegistration { return { name: "Plant", @@ -289,9 +289,9 @@ describe("workflow ambient Git authentication", () => { expect(served.requests[0]?.credentialed).toBe(false); expect(served.requests.some((request) => request.accepted)).toBe(true); - // What was retained is the credential-free locator, unchanged. - const [retained] = yield* retainedRepositories(database); - expect(retained?.locator).toBe(served.locator); + // What was saved is the credential-free locator, unchanged. + const [saved] = yield* savedRepositories(database); + expect(saved?.locator).toBe(served.locator); expect(subcommands(counting.counters)).toContain("clone"); }); }); @@ -318,7 +318,7 @@ describe("workflow ambient Git authentication", () => { // is the failed establishment, which is what makes a later attempt a new // attempt rather than a replay of a completion nobody reached. expect(served.requests.every((request) => !request.accepted)).toBe(true); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); const events = yield* compositionEvents(database); expect(events).toHaveLength(1); expect(JSON.stringify(events[0])).toContain('"status":"err"'); @@ -371,7 +371,7 @@ describe("workflow ambient Git authentication", () => { // Two live provider invocations — the Repository's creation and the // Push's reconciliation — and therefore two sessions, each for this - // Repository's own retained locator. + // Repository's own saved locator. expect(recorded.locators).toEqual([served.locator, served.locator]); // Three commands left the materialization: the clone, the push and the @@ -439,7 +439,7 @@ describe("workflow ambient Git authentication", () => { expect(forSecond.every((request) => !request.accepted)).toBe(true); // And its own word, because acquisition is what the classification reads. expect(causedBy(failure, isRepositoryRefusal)?.reason).toBe("authentication-unavailable"); - expect((yield* retainedRepositories(database)).map((entry) => entry.record.name)).toEqual([ + expect((yield* savedRepositories(database)).map((entry) => entry.record.name)).toEqual([ "reachable", ]); }); @@ -495,19 +495,19 @@ describe("workflow ambient Git authentication", () => { countingOptions(counting), ); - // Rendered output, every journaled event, the retained records, the whole + // Rendered output, every journaled event, the saved records, the whole // command list this run issued, and the Workspace's own configuration. // Each is reduced to a verdict before it is asserted. expect(carriesCredential(String(output))).toBe(false); expect(carriesCredential(JSON.stringify(yield* compositionEvents(database)))).toBe(false); expect(carriesCredential(JSON.stringify(yield* gitHostOutcomes(database)))).toBe(false); - expect(carriesCredential(JSON.stringify(yield* retainedRepositories(database)))).toBe(false); + expect(carriesCredential(JSON.stringify(yield* savedRepositories(database)))).toBe(false); expect(carriesCredential(JSON.stringify(counting.counters.commands))).toBe(false); - const [retained] = yield* retainedRepositories(database); + const [saved] = yield* savedRepositories(database); const config = yield* workspaceText( database, - `${retained?.record.checkoutPath ?? ""}/.git/config`, + `${saved?.record.checkoutPath ?? ""}/.git/config`, ); expect(carriesCredential(config)).toBe(false); }); @@ -654,8 +654,8 @@ describe("workflow ambient authentication classification", () => { expect(redirected.every((request) => !request.credentialed)).toBe(true); expect(redirected).toHaveLength(0); - // Nothing was retained from a transport that ended up elsewhere. - expect(yield* retainedRepositories(database)).toHaveLength(0); + // Nothing was saved from a transport that ended up elsewhere. + expect(yield* savedRepositories(database)).toHaveLength(0); }); }); @@ -678,7 +678,7 @@ describe("workflow ambient authentication classification", () => { // helper was ever reached says nothing: what this run knows is that the // locator could not be used. expect(causedBy(failure, isRepositoryRefusal)?.reason).toBe("invalid-locator"); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); }); }); @@ -750,7 +750,7 @@ describe("workflow ambient authentication containment", () => { * does run the planted helper. A setting Git would have ignored anyway would * prove nothing. */ - it("cannot be replaced by a credential helper planted in retained configuration", function* () { + it("cannot be replaced by a credential helper planted in saved configuration", function* () { const root = yield* useStorageRoot(); const witness = yield* useTempDirectory("xmd-ambient-helper-"); const sentinel = `${witness}/helper-ran`; @@ -927,11 +927,11 @@ describe("workflow ambient authentication cancellation", () => { expect(yield* present(`${gate}.started`)).toBe(true); expect(yield* home.operations()).toEqual(["get"]); - // No transport was opened, nothing was retained, and the working + // No transport was opened, nothing was saved, and the working // directory the acquisition needed is gone. expect(served.requests).toHaveLength(0); expect(subcommands(counting.counters)).not.toContain("clone"); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); expect(watcher.surviving()).toEqual([]); }); }); @@ -1002,7 +1002,7 @@ describe("workflow ambient authentication cancellation", () => { // completion — a cancelled clone is not a repository this run has. expect(recorded.opened).toEqual([served.locator]); expect(recorded.disposed).toEqual([served.locator]); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); expect(watcher.surviving()).toEqual([]); // What must not happen is an authentication artifact being removed while @@ -1111,7 +1111,7 @@ describe("workflow ambient authentication cancellation", () => { }); expect(recorded.opened).toHaveLength(1); - // Nothing was retained, so the next attempt is a first attempt — and it + // Nothing was saved, so the next attempt is a first attempt — and it // opens its own session rather than continuing under one nobody re-proved. block = false; const output = yield* runDocument(database, source, countingOptions(counting)); @@ -1176,7 +1176,7 @@ describe("workflow ambient authentication mechanisms", () => { expect(session.mechanism).toBe("none"); }); - it("fixes the settings a retained repository could otherwise name", function* () { + it("fixes the settings a saved repository could otherwise name", function* () { const home = yield* useInvokingHome([]); const session = yield* denoGitAuthentication({ ambient: home.ambient, @@ -1277,7 +1277,7 @@ describe("workflow GitHub source sessions", () => { const counting = counted("from-host"); // Constructed and kept, exactly as installed middleware keeps one, and // never opened. A source that read a credential to exist would be an - // identity retained for a middleware's whole lifetime. + // identity saved for a middleware's whole lifetime. gitHubSource(counting.access); expect(counting.reads).toEqual([]); expect(counting.sent).toEqual([]); diff --git a/packages/git/tests/git-add-durability.test.ts b/packages/git/tests/git-add-durability.test.ts index 57a155594..575faf345 100644 --- a/packages/git/tests/git-add-durability.test.ts +++ b/packages/git/tests/git-add-durability.test.ts @@ -2,12 +2,12 @@ * Tier WF — what a `` retains, replays and refuses to guess. * * These tests inspect the run's database rather than an index. A replay must - * stage nothing and append no second transition; a retained result that no + * stage nothing and append no second transition; a saved result that no * longer describes the invocation it was recorded for must stop the run; a * cancelled or unretainable staging must leave the frontier exactly as it was. * * Every replay happens after the remote is deleted, so what continues a run is - * what the run retained or nothing does. + * what the run saved or nothing does. */ import { describe, it } from "@executablemd/test-support/bdd"; @@ -53,7 +53,7 @@ import { gitOutcomes, physicalGitApiCopy, raised, - retainedRepositories, + savedRepositories, runDocument, stagedPaths, subcommands, @@ -114,7 +114,7 @@ function tableNames(path: string): string[] { } } -/** Damage the one retained add result, refusing to continue if it damaged none. */ +/** Damage the one saved add result, refusing to continue if it damaged none. */ function damageAddResult(path: string, damage: (record: Record) => void): void { let rewritten = 0; tamper(path, (database) => { @@ -191,7 +191,7 @@ describe("workflow Git.Add durability", () => { }); /** - * Every way a retained add result can stop describing its invocation. + * Every way a saved add result can stop describing its invocation. * * Shape, then meaning: an object id that is not one, a checkout that is not * the one this request selects or is not where placement puts it, pathspecs @@ -293,7 +293,7 @@ describe("workflow Git.Add durability", () => { ]; for (const { name, damage } of DAMAGE) { - it(`fails a replay whose retained result ${name}`, function* () { + it(`fails a replay whose saved result ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -357,14 +357,14 @@ describe("workflow Git.Add durability", () => { expect(yield* stagedPaths(database, yield* checkoutOf(database))).toEqual([]); // Nothing Git printed, nowhere it ran, and nothing it found. - const retained = `${outcome?.name} ${outcome?.message}`; - expect(retained).toContain("ignored-pathspec"); - expect(retained).not.toContain("ignored.txt"); - expect(retained).not.toContain("gitignore"); - expect(retained).not.toContain("hint:"); - expect(retained).not.toContain(tmpdir()); + const saved = `${outcome?.name} ${outcome?.message}`; + expect(saved).toContain("ignored-pathspec"); + expect(saved).not.toContain("ignored.txt"); + expect(saved).not.toContain("gitignore"); + expect(saved).not.toContain("hint:"); + expect(saved).not.toContain(tmpdir()); for (const materialization of live.counters.roots) { - expect(retained).not.toContain(materialization); + expect(saved).not.toContain(materialization); } const published = publishedRoots(path); @@ -417,7 +417,7 @@ describe("workflow Git.Add durability", () => { yield* task.halt(); }); - expect(yield* retainedRepositories(database)).toHaveLength(1); + expect(yield* savedRepositories(database)).toHaveLength(1); expect(yield* gitEvents(database)).toHaveLength(0); expect(committedRoot(path)).toBe(latestRoot(path)); expect(yield* stagedPaths(database, yield* checkoutOf(database))).toEqual([]); @@ -486,19 +486,19 @@ describe("workflow Git.Add durability", () => { expect(tableNames(runPath(root, "staged"))).toEqual(tableNames(runPath(root, "untouched"))); // Creation identity does not move when an index does. - const [before] = yield* retainedRepositories(untouched); - const [after] = yield* retainedRepositories(staged); + const [before] = yield* savedRepositories(untouched); + const [after] = yield* savedRepositories(staged); expect(after?.record).toEqual(before?.record); const [outcome] = yield* gitOutcomes(staged); - const retained = JSON.stringify(outcome?.record); + const saved = JSON.stringify(outcome?.record); for (const materialization of counting.counters.roots) { - expect(retained).not.toContain(materialization); + expect(saved).not.toContain(materialization); } - expect(retained).not.toContain(tmpdir()); - // The pathspecs are retained; what Git found beyond them is not. - expect(retained).toContain("added.txt"); - expect(retained).not.toContain("nested/note.md"); + expect(saved).not.toContain(tmpdir()); + // The pathspecs are saved; what Git found beyond them is not. + expect(saved).toContain("added.txt"); + expect(saved).not.toContain("nested/note.md"); }); }); }); @@ -599,11 +599,11 @@ function probe( }; } -/** The primary checkout's Workspace path, once this run has retained one. */ +/** The primary checkout's Workspace path, once this run has saved one. */ function* checkoutOf(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } return repository.record.checkoutPath; } diff --git a/packages/git/tests/git-add.test.ts b/packages/git/tests/git-add.test.ts index f8d2860fc..80a7516cd 100644 --- a/packages/git/tests/git-add.test.ts +++ b/packages/git/tests/git-add.test.ts @@ -3,7 +3,7 @@ * * What is under test is staging that survives in the run's own Workspace: which * pathspecs Git received, which checkout received them, and which directory it - * read them relative to. The discriminating observation is the retained + * read them relative to. The discriminating observation is the saved * checkout's index, read back through a disposable export the way the provider * reads it — an index-tree digest says something changed, and only the index * says what. @@ -53,8 +53,8 @@ import { gitOutcomes, raised, removeWorkspacePath, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, stagedPaths, subcommands, @@ -112,15 +112,15 @@ function document(locator: string, ...lines: string[]): string { return [``, ...lines, ""].join("\n"); } -/** What a retained result is read back for. */ +/** What a saved result is read back for. */ function* expectation( database: WorkflowRunDatabase, paths: readonly string[], within = "", ): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository to read a result against"); + throw new Error("the run saved no repository to read a result against"); } return { repository: repository.record, @@ -157,11 +157,11 @@ function runForged( }); } -/** The primary checkout's Workspace path, once this run has retained one. */ +/** The primary checkout's Workspace path, once this run has saved one. */ function* checkout(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } return repository.record.checkoutPath; } @@ -194,17 +194,14 @@ describe("workflow Git.Add staging", () => { const [outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitAddResult( - outcome?.record, - yield* expectation(database, ["added.txt"]), - ); - expect(retained?.paths).toEqual(["added.txt"]); - expect(retained?.checkout.worktreeName).toBe(null); + const saved = parseGitAddResult(outcome?.record, yield* expectation(database, ["added.txt"])); + expect(saved?.paths).toEqual(["added.txt"]); + expect(saved?.checkout.worktreeName).toBe(null); // Staging moves the index and nothing else. - expect(retained?.before.branch).toBe(retained?.after.branch); - expect(retained?.before.commit).toBe(retained?.after.commit); - expect(retained?.before.headTree).toBe(retained?.after.headTree); - expect(retained?.before.indexTree).not.toBe(retained?.after.indexTree); + expect(saved?.before.branch).toBe(saved?.after.branch); + expect(saved?.before.commit).toBe(saved?.after.commit); + expect(saved?.before.headTree).toBe(saved?.after.headTree); + expect(saved?.before.indexTree).not.toBe(saved?.after.indexTree); }); }); @@ -234,9 +231,9 @@ describe("workflow Git.Add staging", () => { "which.txt", ]); const [outcome] = yield* gitOutcomes(database); - const retained = parseGitAddResult(outcome?.record, yield* expectation(database, paths)); + const saved = parseGitAddResult(outcome?.record, yield* expectation(database, paths)); // What the document wrote, not what Git made of it. - expect(retained?.paths).toEqual(paths); + expect(saved?.paths).toEqual(paths); }); }); @@ -263,12 +260,12 @@ describe("workflow Git.Add staging", () => { ); expect(yield* stagedPaths(database, yield* checkout(database))).toEqual(["added.txt"]); const [outcome] = yield* gitOutcomes(database); - const retained = parseGitAddResult( + const saved = parseGitAddResult( outcome?.record, yield* expectation(database, ["added.txt"]), ); - expect(retained?.paths).toEqual(["added.txt"]); - trees.push(String(retained?.after.indexTree)); + expect(saved?.paths).toEqual(["added.txt"]); + trees.push(String(saved?.after.indexTree)); } expect(trees[0]).toBe(trees[1]); }); @@ -282,7 +279,7 @@ describe("workflow Git.Add staging", () => { // executions of one run. The first execution's staging fails, which retains // nothing and leaves the run partial; the Workspace fixture then removes the // file, and the identical document runs again — which is also what "the - // operation may run again from the previous retained root" means. + // operation may run again from the previous saved root" means. const source = document( remote.locator, ``, @@ -373,13 +370,13 @@ describe("workflow Git.Add staging", () => { // the directory is untouched. expect(yield* stagedPaths(nested, yield* checkout(nested))).toEqual(["nested/note.md"]); const [outcome] = yield* gitOutcomes(nested); - const retained = parseGitAddResult( + const saved = parseGitAddResult( outcome?.record, yield* expectation(nested, ["."], "/nested"), ); - expect(retained?.paths).toEqual(["."]); + expect(saved?.paths).toEqual(["."]); // The checkout is the Repository's; the directory is where Git ran. - expect(retained?.checkout.checkoutPath).toBe(yield* checkout(nested)); + expect(saved?.checkout.checkoutPath).toBe(yield* checkout(nested)); // The same document at the checkout root stages both, which is what makes // the reading above a claim about the working directory. @@ -481,7 +478,7 @@ describe("workflow Git.Add selection", () => { ), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); expect(yield* stagedPaths(database, String(worktree?.checkoutPath))).toEqual(["added.txt"]); // The Repository's own checkout staged nothing. expect(yield* stagedPaths(database, yield* checkout(database))).toEqual([]); @@ -518,7 +515,7 @@ describe("workflow Git.Add selection", () => { ), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); expect(yield* stagedPaths(database, String(worktree?.checkoutPath))).toEqual([ "nested/note.md", ]); @@ -559,11 +556,11 @@ describe("workflow Git.Add selection", () => { ].join("\n"); yield* withStorage(root, function* () { - const unretained = yield* createRun({ runId: "ghost" }); + const unsaved = yield* createRun({ runId: "ghost" }); const counting = countingHost(); - const ghost = yield* raised(runForged(unretained, FORGED, source, countingOptions(counting))); + const ghost = yield* raised(runForged(unsaved, FORGED, source, countingOptions(counting))); expect(causedBy(ghost, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); - expect(yield* gitEvents(unretained)).toHaveLength(0); + expect(yield* gitEvents(unsaved)).toHaveLength(0); // A real name carrying a substituted member is refused for the member. const substituted = yield* createRun({ runId: "substituted" }); @@ -571,7 +568,7 @@ describe("workflow Git.Add selection", () => { runForged(substituted, { ...FORGED, name: "project" }, source, countingOptions(counting)), ); expect(causedBy(failure, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); - expect(yield* retainedRepositories(substituted)).toHaveLength(1); + expect(yield* savedRepositories(substituted)).toHaveLength(1); expect(yield* gitEvents(substituted)).toHaveLength(0); expect(subcommands(counting.counters)).not.toContain("add"); }); @@ -789,7 +786,7 @@ describe("workflow Git.Add refusals", () => { * A JavaScript string is UTF-16 code units, and an unpaired surrogate is one a * document can write. The way to Git is UTF-8 — a process argument list — and * encoding an unpaired surrogate there replaces it with U+FFFD, so Git would - * receive a different string from the one the run retained. The checkout below + * receive a different string from the one the run saved. The checkout below * really holds a U+FFFD-named file, which is what makes the substitution * observable rather than theoretical: it would have been staged. */ @@ -907,11 +904,8 @@ describe("workflow Git.Add pathspec text", () => { expect(yield* stagedPaths(database, yield* checkout(database))).toEqual([REPLACEMENT]); const [outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitAddResult( - outcome?.record, - yield* expectation(database, [REPLACEMENT]), - ); - expect(retained?.paths).toEqual([REPLACEMENT]); + const saved = parseGitAddResult(outcome?.record, yield* expectation(database, [REPLACEMENT])); + expect(saved?.paths).toEqual([REPLACEMENT]); }); }); }); @@ -1010,19 +1004,19 @@ describe("workflow Git.Add request ownership", () => { // sitting right there, untouched. expect(yield* stagedPaths(database, yield* checkout(database))).toEqual(["original.txt"]); - // The retained result and the effect's own identity say the same thing. + // The saved result and the effect's own identity say the same thing. const [outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitAddResult( + const saved = parseGitAddResult( outcome?.record, yield* expectation(database, ["original.txt"]), ); - expect(retained?.paths).toEqual(["original.txt"]); + expect(saved?.paths).toEqual(["original.txt"]); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const record = repository?.record; if (record === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } const [event] = yield* gitEvents(database); expect(event?.type === "yield" ? event.description.configuration : undefined).toBe( diff --git a/packages/git/tests/git-commit-durability.test.ts b/packages/git/tests/git-commit-durability.test.ts index d729fafd1..8699efabe 100644 --- a/packages/git/tests/git-commit-durability.test.ts +++ b/packages/git/tests/git-commit-durability.test.ts @@ -3,12 +3,12 @@ * * These tests inspect the run's database rather than a repository. A replay * must write no object, read no clock and append no second transition; a - * retained result that no longer describes the invocation it was recorded for + * saved result that no longer describes the invocation it was recorded for * must stop the run; a cancelled or unretainable commit must leave the frontier * exactly as it was. * * Every replay happens after the remote is deleted, so what continues a run is - * what the run retained or nothing does. + * what the run saved or nothing does. */ import { describe, it } from "@executablemd/test-support/bdd"; @@ -54,7 +54,7 @@ import { headCommit, physicalGitApiCopy, raised, - retainedRepositories, + savedRepositories, runDocument, stagedPaths, subcommands, @@ -107,9 +107,9 @@ function isGitFailure(value: unknown): value is GitOperationError { } function* checkoutOf(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } return repository.record.checkoutPath; } @@ -127,7 +127,7 @@ function tableNames(path: string): string[] { } } -/** Damage the one retained commit result, refusing to continue if it damaged none. */ +/** Damage the one saved commit result, refusing to continue if it damaged none. */ function damageCommitResult(path: string, damage: (record: Record) => void): void { let rewritten = 0; tamper(path, (database) => { @@ -183,7 +183,7 @@ describe("workflow Git.Commit durability", () => { }); /** - * Every way a retained commit result can stop describing its invocation. + * Every way a saved commit result can stop describing its invocation. * * Shape, then meaning: an object id that is not one, a checkout that is not * the one this request selects, message evidence that is not the evidence of @@ -298,7 +298,7 @@ describe("workflow Git.Commit durability", () => { ]; for (const { name, damage } of DAMAGE) { - it(`fails a replay whose retained result ${name}`, function* () { + it(`fails a replay whose saved result ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -349,12 +349,12 @@ describe("workflow Git.Commit durability", () => { expect(committedRoot(path)).toBe(latestRoot(path)); // Nothing Git printed, nowhere it ran. - const retained = `${outcome?.name} ${outcome?.message}`; - expect(retained).toContain("empty-index"); - expect(retained).not.toContain("nothing to commit"); - expect(retained).not.toContain(tmpdir()); + const saved = `${outcome?.name} ${outcome?.message}`; + expect(saved).toContain("empty-index"); + expect(saved).not.toContain("nothing to commit"); + expect(saved).not.toContain(tmpdir()); for (const materialization of live.counters.roots) { - expect(retained).not.toContain(materialization); + expect(saved).not.toContain(materialization); } const published = publishedRoots(path); @@ -483,19 +483,19 @@ describe("workflow Git.Commit durability", () => { expect(tableNames(runPath(root, "recorded"))).toEqual(tableNames(runPath(root, "untouched"))); // Creation identity does not move when a branch tip does. - const [before] = yield* retainedRepositories(untouched); - const [after] = yield* retainedRepositories(recorded); + const [before] = yield* savedRepositories(untouched); + const [after] = yield* savedRepositories(recorded); expect(after?.record).toEqual(before?.record); const [, outcome] = yield* gitOutcomes(recorded); - const retained = JSON.stringify(outcome?.record); + const saved = JSON.stringify(outcome?.record); for (const materialization of counting.counters.roots) { - expect(retained).not.toContain(materialization); + expect(saved).not.toContain(materialization); } - expect(retained).not.toContain(tmpdir()); + expect(saved).not.toContain(tmpdir()); // The message is described rather than kept. - expect(retained).not.toContain(MESSAGE); - expect(retained).toContain("messageDigest"); + expect(saved).not.toContain(MESSAGE); + expect(saved).toContain("messageDigest"); }); }); }); diff --git a/packages/git/tests/git-commit.test.ts b/packages/git/tests/git-commit.test.ts index 59a82025a..adeda4ef8 100644 --- a/packages/git/tests/git-commit.test.ts +++ b/packages/git/tests/git-commit.test.ts @@ -4,7 +4,7 @@ * What is under test is a commit that survives in the run's own Workspace: the * object it wrote, the bytes of its message, the instant it recorded and the * value it handed back. The discriminating observation is the commit object - * itself, read out of the retained checkout with `cat-file` — a retained result + * itself, read out of the saved checkout with `cat-file` — a saved result * says what this provider believes, and only the object says what Git did. */ @@ -62,8 +62,8 @@ import { headCommit, inCheckout, raised, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, stagedPaths, subcommands, @@ -137,16 +137,16 @@ function staging(locator: string, message: string, ...extra: string[]): string { ); } -/** What a retained result is read back for. */ +/** What a saved result is read back for. */ function* expectation( database: WorkflowRunDatabase, message: string, source: GitCommitMessageSource, workingDirectory?: string, ): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository to read a result against"); + throw new Error("the run saved no repository to read a result against"); } const evidence = gitCommitMessageEvidence(message); return { @@ -158,11 +158,11 @@ function* expectation( }; } -/** The primary checkout's Workspace path, once this run has retained one. */ +/** The primary checkout's Workspace path, once this run has saved one. */ function* checkout(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } return repository.record.checkoutPath; } @@ -223,20 +223,20 @@ describe("workflow Git.Commit", () => { const [, outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, message, "prop"), ); - expect(retained?.commit).toBe(object.commit); - expect(retained?.parent).toBe(object.parents[0]); - expect(retained?.tree).toBe(object.tree); - expect(retained?.checkout.repositoryName).toBe("project"); - expect(retained?.checkout.worktreeName).toBe(null); - expect(retained?.messageSource).toBe("prop"); - expect(retained?.after.commit).toBe(object.commit); - expect(retained?.after.headTree).toBe(object.tree); - expect(retained?.after.indexTree).toBe(object.tree); - expect(retained?.before.commit).toBe(retained?.parent); + expect(saved?.commit).toBe(object.commit); + expect(saved?.parent).toBe(object.parents[0]); + expect(saved?.tree).toBe(object.tree); + expect(saved?.checkout.repositoryName).toBe("project"); + expect(saved?.checkout.worktreeName).toBe(null); + expect(saved?.messageSource).toBe("prop"); + expect(saved?.after.commit).toBe(object.commit); + expect(saved?.after.headTree).toBe(object.tree); + expect(saved?.after.indexTree).toBe(object.tree); + expect(saved?.before.commit).toBe(saved?.parent); // Nothing is staged afterwards: the index and HEAD describe one tree. expect(yield* stagedPaths(database, yield* checkout(database))).toEqual([]); @@ -323,11 +323,11 @@ describe("workflow Git.Commit", () => { const [, outcome] = yield* gitOutcomes(database); const object = yield* headCommit(database, yield* checkout(database)); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "timed\n", "prop"), ); - const second = retained?.committedAt; + const second = saved?.committedAt; expect(typeof second).toBe("number"); expect(object.authorTime).toBe(`${second} +0000`); expect(object.committerTime).toBe(`${second} +0000`); @@ -364,11 +364,11 @@ describe("workflow Git.Commit message", () => { expect(object.message).toBe("\nwritten as content\n"); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "\nwritten as content\n", "children"), ); - expect(retained?.messageSource).toBe("children"); + expect(saved?.messageSource).toBe("children"); }); }); @@ -401,12 +401,12 @@ describe("workflow Git.Commit message", () => { ); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, object.message, "both"), ); - expect(retained?.messageSource).toBe("both"); - expect(retained?.messageLength).toBe(new TextEncoder().encode(object.message).length); + expect(saved?.messageSource).toBe("both"); + expect(saved?.messageLength).toBe(new TextEncoder().encode(object.message).length); }); }); @@ -415,7 +415,7 @@ describe("workflow Git.Commit message", () => { * * `--cleanup=verbatim` is what makes this hold. Git's own default cleanup * strips whitespace at the end of every line and empty lines at the start, - * which would commit prose nobody wrote and leave the retained digest + * which would commit prose nobody wrote and leave the saved digest * describing bytes the repository does not hold. */ it("commits the canonical bytes and nothing Git would have tidied", function* () { @@ -435,14 +435,14 @@ describe("workflow Git.Commit message", () => { const [, outcome] = yield* gitOutcomes(database); const evidence = gitCommitMessageEvidence(canonical); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, canonical, "prop"), ); - expect(retained?.messageDigest).toBe(evidence.digest); - expect(retained?.messageLength).toBe(evidence.length); + expect(saved?.messageDigest).toBe(evidence.digest); + expect(saved?.messageLength).toBe(evidence.length); - // The message itself is nowhere in what the run retained. + // The message itself is nowhere in what the run saved. expect(JSON.stringify(outcome?.record)).not.toContain("keep the indent"); }); }); @@ -648,10 +648,10 @@ describe("workflow Git.Commit leading content", () => { source: GitCommitMessageSource, message: string, ): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const record = repository?.record; if (record === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } return gitOperationFingerprint([ record.name, @@ -694,14 +694,14 @@ describe("workflow Git.Commit leading content", () => { // And the evidence describes exactly those bytes. const evidence = gitCommitMessageEvidence(message); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, message, "children"), ); - expect(retained?.messageSource).toBe("children"); - expect(retained?.messageDigest).toBe(evidence.digest); - expect(retained?.messageLength).toBe(evidence.length); - expect(retained?.messageLength).toBe(8); + expect(saved?.messageSource).toBe("children"); + expect(saved?.messageDigest).toBe(evidence.digest); + expect(saved?.messageLength).toBe(evidence.length); + expect(saved?.messageLength).toBe(8); // The identity is the identity of the bytes that were preserved, and not // the identity the trimmed message would have had. @@ -737,13 +737,13 @@ describe("workflow Git.Commit leading content", () => { const evidence = gitCommitMessageEvidence(message); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, message, "both"), ); - expect(retained?.messageSource).toBe("both"); - expect(retained?.messageDigest).toBe(evidence.digest); - expect(retained?.messageLength).toBe(evidence.length); + expect(saved?.messageSource).toBe("both"); + expect(saved?.messageDigest).toBe(evidence.digest); + expect(saved?.messageLength).toBe(evidence.length); const [, event] = yield* gitEvents(database); const named = event?.type === "yield" ? event.description.configuration : undefined; @@ -775,11 +775,11 @@ describe("workflow Git.Commit leading content", () => { expect(object.message).toBe("only the prop\n"); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "only the prop\n", "prop"), ); - expect(retained?.messageSource).toBe("prop"); + expect(saved?.messageSource).toBe("prop"); }); }); }); @@ -807,22 +807,22 @@ describe("workflow Git.Commit selection", () => { ), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); if (worktree === undefined) { - throw new Error("the run retained no worktree"); + throw new Error("the run saved no worktree"); } const object = yield* headCommit(database, worktree.checkoutPath); expect(object.branch).toBe("feature/new"); expect(object.message).toBe("inside the worktree\n"); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "inside the worktree\n", "prop", worktree.checkoutPath), ); - expect(retained?.checkout.worktreeName).toBe("implementation"); - expect(retained?.checkout.checkoutPath).toBe(worktree.checkoutPath); - expect(retained?.commit).toBe(object.commit); + expect(saved?.checkout.worktreeName).toBe("implementation"); + expect(saved?.checkout.checkoutPath).toBe(worktree.checkoutPath); + expect(saved?.commit).toBe(object.commit); // The primary checkout did not move. const primary = yield* headCommit(database, yield* checkout(database)); @@ -862,11 +862,11 @@ describe("workflow Git.Commit selection", () => { expect(inCommit).toEqual(["added.txt"]); const [, outcome] = yield* gitOutcomes(database); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "written inside nested\n", "prop", `${path}/nested`), ); - expect(retained?.checkout.checkoutPath).toBe(path); + expect(saved?.checkout.checkoutPath).toBe(path); }); }); @@ -906,12 +906,12 @@ describe("workflow Git.Commit selection", () => { ].join("\n"); yield* withStorage(root, function* () { - const unretained = yield* createRun({ runId: "ghost" }); + const unsaved = yield* createRun({ runId: "ghost" }); const counting = countingHost(); - const ghost = yield* raised(runForged(unretained, FORGED, source, countingOptions(counting))); + const ghost = yield* raised(runForged(unsaved, FORGED, source, countingOptions(counting))); expect(causedBy(ghost, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); expect(causedBy(ghost, isGitFailure)).toBe(undefined); - expect(yield* gitEvents(unretained)).toHaveLength(0); + expect(yield* gitEvents(unsaved)).toHaveLength(0); // A real name carrying a substituted member is refused for the member. const substituted = yield* createRun({ runId: "substituted" }); @@ -919,7 +919,7 @@ describe("workflow Git.Commit selection", () => { runForged(substituted, { ...FORGED, name: "project" }, source, countingOptions(counting)), ); expect(causedBy(failure, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); - expect(yield* retainedRepositories(substituted)).toHaveLength(1); + expect(yield* savedRepositories(substituted)).toHaveLength(1); expect(yield* gitEvents(substituted)).toHaveLength(0); expect(subcommands(counting.counters)).not.toContain("commit"); }); @@ -1085,10 +1085,10 @@ describe("workflow Git.Commit transition", () => { const database = yield* createRun(); yield* runDocument(database, staging(remote.locator, "identified")); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const record = repository?.record; if (record === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } const [, event] = yield* gitEvents(database); expect(event?.type === "yield" ? event.description.configuration : undefined).toBe( @@ -1205,12 +1205,12 @@ describe("workflow Git.Commit request ownership", () => { const [, outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "admitted by the operation\n", "prop"), ); - expect(retained?.commit).toBe(object.commit); - expect(retained?.checkout.repositoryName).toBe("project"); + expect(saved?.commit).toBe(object.commit); + expect(saved?.checkout.repositoryName).toBe("project"); }); }); }); @@ -1277,7 +1277,7 @@ describe("workflow Git.Commit repository configuration", () => { }); } - /** Add these lines to the retained checkout's own configuration. */ + /** Add these lines to the saved checkout's own configuration. */ function* configure( database: WorkflowRunDatabase, checkoutPath: string, @@ -1300,7 +1300,7 @@ describe("workflow Git.Commit repository configuration", () => { * is outside the materialization, which is also outside anything a rollback * could take back. */ - it("runs no hook a retained .git/config activates", function* () { + it("runs no hook a saved .git/config activates", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const outside = yield* useTempDirectory("xmd-commit-hooks-"); @@ -1349,13 +1349,13 @@ describe("workflow Git.Commit repository configuration", () => { const [, outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "configured commit\n", "prop"), ); - expect(retained?.commit).toBe(object.commit); - expect(retained?.tree).toBe(object.tree); - expect(retained?.parent).toBe(object.parents[0]); + expect(saved?.commit).toBe(object.commit); + expect(saved?.tree).toBe(object.tree); + expect(saved?.parent).toBe(object.parents[0]); // Replaying a run whose checkout is configured this way runs no Git, so // there is no second chance for a hook either. @@ -1379,7 +1379,7 @@ describe("workflow Git.Commit repository configuration", () => { * Neither is skipped by a flag — both are configuration, and configuration is * what a checkout carries. */ - it("runs no signing or monitor program a retained .git/config names", function* () { + it("runs no signing or monitor program a saved .git/config names", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const outside = yield* useTempDirectory("xmd-commit-programs-"); @@ -1423,11 +1423,11 @@ describe("workflow Git.Commit repository configuration", () => { const [, outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitCommitResult( + const saved = parseGitCommitResult( outcome?.record, yield* expectation(database, "configured commit\n", "prop"), ); - expect(retained?.commit).toBe(object.commit); + expect(saved?.commit).toBe(object.commit); }); }); }); diff --git a/packages/git/tests/git-host-effect.test.ts b/packages/git/tests/git-host-effect.test.ts index 1e952e8b9..e5c1045cc 100644 --- a/packages/git/tests/git-host-effect.test.ts +++ b/packages/git/tests/git-host-effect.test.ts @@ -35,7 +35,7 @@ import { } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import type { ExecutionInstallation } from "@executablemd/core/host"; -import { retainedWorkflowInstallation } from "../../workflow/src/run.ts"; +import { savedWorkflowInstallation } from "../../workflow/src/run.ts"; import { gitPlugin } from "../src/plugin.ts"; import type { WorkflowRun } from "../../workflow/src/run.ts"; import { GIT_HOST_EFFECT } from "../src/git-host/effect.ts"; @@ -68,7 +68,7 @@ import type { * Asked of the Plugin value rather than assembled here, so what these cases * exercise is the same contribution a command gets — one Plugin value, and an * admission that derives this execution's identities from this execution's own - * retained history. + * saved history. */ function* gitPluginAdmissions(): Operation { const install = gitPlugin.install; @@ -264,9 +264,9 @@ interface RunOptions { } /** - * One document execution of ``, under one retained run. + * One document execution of ``, under one saved run. * - * The run is retained rather than allocated so that every execution in a test + * The run is saved rather than allocated so that every execution in a test * carries the same external identity, which is what the reconciliation is keyed * on. The document's failure is captured rather than raised: what each test * measures is the provider traffic and the journal, and both outlive the @@ -299,7 +299,7 @@ function collectDocument(stream: DurableStream): Operation { function* collectSource(source: string, stream: DurableStream): Operation { return yield* collect( yield* executeInstalled({ ...inlineSource(source), stream }, [ - retainedWorkflowInstallation(RUN), + savedWorkflowInstallation(RUN), yield* gitPluginAdmissions(), ]), ); @@ -571,7 +571,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { expect(gitHostYields(stream.snapshot())).toEqual([]); }); - it("GH8: a changed request cannot consume a retained completion", function* () { + it("GH8: a changed request cannot consume a saved completion", function* () { const stream = new InMemoryStream(); const host = recordingProvider(answering(Ok(ABSENT)), answering(Ok(PERFORMED))); const first = yield* runDocument({ stream, provider: host.provider }); @@ -1038,7 +1038,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { it("GH14: a counterfeit context cannot choose a Git-host identity", function* () { // A fork's journal holds records its source wrote, so the effect at a - // retained position is named by the identity that record holds. What must + // saved position is named by the identity that record holds. What must // never choose it is a binding: `DurableContext` is a public stable name, // and a stateful replacement can forge one replay-index answer and delegate // the rest. @@ -1093,7 +1093,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { } }); - it("GH15: an inherited identity answers one retained call, never the live one after it", function* () { + it("GH15: an inherited identity answers one saved call, never the live one after it", function* () { // `reconcileGitHostEffect()` is a public surface, so one expansion may reach // it twice. A fork whose inherited prefix ends after the first of those // calls must not lend the source's identity to the second: that call is @@ -1114,7 +1114,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { expect(gitHostYields(seedStream.snapshot())).toHaveLength(2); // What a fork inherits: the prefix ending after the *first* record, written - // under the source's identity. The second call has nothing retained. + // under the source's identity. The second call has nothing saved. const rewritten = yield* inheritedHistory(partial(seedStream.snapshot()), sourceRunId); const upTo = rewritten.findIndex( (event) => event.type === "yield" && event.description.type === GIT_HOST_EFFECT, @@ -1154,8 +1154,8 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { expect(record?.request.identity.runId).toBe(RUN.runId); }); - it("GH16: a live Git-host effect never stands on retained history", function* () { - // Two retained records. The call being made asks what the *second* one + it("GH16: a live Git-host effect never stands on saved history", function* () { + // Two saved records. The call being made asks what the *second* one // answers, while the record at this position is the first — and public // divergence policy chooses to run live rather than refuse. Neither the // source's identity nor a live provider call may come of that: the position @@ -1208,7 +1208,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { }); // It borrowed nothing — the record at this position does not answer what it - // asks — and it did not perform either. Standing where retained history + // asks — and it did not perform either. Standing where saved history // already holds a Git-host record and not replaying it means the effect // that record describes has already happened at a service this journal does // not enclose, so nothing is asked of the Git host and nothing is appended. @@ -1282,7 +1282,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { // The identities travel in a stable-named slot so a second physical copy of // this package reads them. A component can bind that name: it can keep the // run and erase what sits beside it, and then the reconciliation finds - // nothing to recognize the retained record by. What it must not be able to + // nothing to recognize the saved record by. What it must not be able to // do is turn that into a live effect at a position the history already // holds one for. // @@ -1313,7 +1313,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { // The queue is bound by name, so a document could rebind it. The run // itself is untouched; only what sits beside it moves. const slot = createContext( - "executablemd.git.git-host.retained-identities", + "executablemd.git.git-host.saved-identities", undefined, ); const genuine = yield* slot.get(); @@ -1350,9 +1350,9 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { } }); - it("GH18: a coroutine that stepped over retained history performs no Git-host effect", function* () { + it("GH18: a coroutine that stepped over saved history performs no Git-host effect", function* () { // The divergence is not at the Git-host position at all. An ordinary - // durable call ahead of it mismatches, run-live steps over the retained + // durable call ahead of it mismatches, run-live steps over the saved // history, and the Git-host record is still sitting there unconsumed. A // guard that only saw the position where replay gave up would let this one // through — and erasing the shared identities is exactly how a caller would @@ -1393,7 +1393,7 @@ describe("Tier GH — shared external Git-host effect reconciliation", () => { * * The generic call is what a later definition can disagree about without * touching the Git-host request at all, which is the shape a divergence ahead - * of retained history has. + * of saved history has. */ function* markedRun(options: { readonly stream: DurableStream; @@ -1411,7 +1411,7 @@ function* markedRun(options: { *fn() { if (options.erase === true) { const slot = createContext( - "executablemd.git.git-host.retained-identities", + "executablemd.git.git-host.saved-identities", undefined, ); const genuine = yield* slot.get(); @@ -1496,7 +1496,7 @@ const COUNTERFEIT_RUN = "counterfeit-run"; * install one. The replacement is *stateful* and armed immediately before the * effect: it forges one completed Git-host entry for the next `peekYield()` and * delegates every later peek to the genuine index, so anything consulting the - * replay index for an identity at that moment sees a retained record while + * replay index for an identity at that moment sees a saved record while * durable execution sees the real position. * * Nothing it does may change which identity the effect uses. @@ -1589,7 +1589,7 @@ function* counterfeitRecord(): Operation { /** * One run's history as another run's, so it can be inherited. * - * Only the Git-host record moves: its retained identity becomes the source's, + * Only the Git-host record moves: its saved identity becomes the source's, * and the durable operation is renamed to the digest that identity produces — * which is exactly what the source itself wrote, and what a fork copies * verbatim. diff --git a/packages/git/tests/git-push-crash.test.ts b/packages/git/tests/git-push-crash.test.ts index dedd9f877..36e949ccb 100644 --- a/packages/git/tests/git-push-crash.test.ts +++ b/packages/git/tests/git-push-crash.test.ts @@ -52,7 +52,7 @@ import { gitHostEvents, gitHostOutcomes, headCommit, - retainedRepositories, + savedRepositories, runWorkflowDocument, subcommands, } from "./support/composition.ts"; @@ -197,7 +197,7 @@ describe("workflow Git.Push across a process boundary", () => { const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); expect(head.commit).toBe(pushedCommit); expect(remoteBranch(remote, PUSH_BRANCH)).toBe(pushedCommit); @@ -352,7 +352,7 @@ describe("workflow Git.Push across a process boundary", () => { yield* runWorkflowDocument(database, pushDocument(served.locator), countingOptions(counting)); // A different chain entirely, asked once — the Repository replays from - // what the crash retained and acquires nothing, so the only acquisition + // what the crash saved and acquires nothing, so the only acquisition // here is the Push's own. expect(yield* resumeHome.operations()).toEqual(["get"]); diff --git a/packages/git/tests/git-push-durability.test.ts b/packages/git/tests/git-push-durability.test.ts index 0abb29db2..563f1198a 100644 --- a/packages/git/tests/git-push-durability.test.ts +++ b/packages/git/tests/git-push-durability.test.ts @@ -2,7 +2,7 @@ * Tier WF — what a `` retains, replays and refuses to guess. * * These tests inspect the run's database and the remote together. A replayed - * push must reach no remote and append no second transition; a retained record + * push must reach no remote and append no second transition; a saved record * that no longer describes the invocation it was recorded for must stop the run * rather than be read around; a cancelled push must leave the journal, the * frontier and the materializations exactly as it found them; and the request @@ -64,8 +64,8 @@ import { inlinePosition, physicalGitApiCopy, raised, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runWorkflowDocument, subcommands, survivingRoots, @@ -171,7 +171,7 @@ function isHostProtocolFailure(value: unknown): value is GitHostProtocolError { } /** - * Damage the one retained reconciliation record, and refuse if it damaged none. + * Damage the one saved reconciliation record, and refuse if it damaged none. * * A tamper that matched nothing leaves the run healthy, and a regression built * on one passes by replaying an undamaged record. @@ -208,13 +208,13 @@ describe("workflow Git.Push durability", () => { yield* runWorkflowDocument(database, source(remote.locator)); const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); expect(remoteBranch(remote, BRANCH)).toBe(head.commit); const published = publishedRoots(path); dropRootClose(path); - // Deleted, so what continues this run is what it retained or nothing. + // Deleted, so what continues this run is what it saved or nothing. yield* remote.remove(); const counting = countingHost(); @@ -227,7 +227,7 @@ describe("workflow Git.Push durability", () => { expect(subcommands(counting.counters)).not.toContain("init"); const effects = yield* gitHostEvents(database); expect(effects).toHaveLength(1); - // The retained effect names the authored `` element itself — + // The saved effect names the authored `` element itself — // diagnostic data beside the identity, which the replay above proved is // still matched by type and name alone. const [effect] = effects; @@ -243,7 +243,7 @@ describe("workflow Git.Push durability", () => { }); /** - * An advance is retained as completely as a creation, and proves nothing + * An advance is saved as completely as a creation, and proves nothing * twice. * * The ancestry behind a fast-forward is a fact this run established once, at @@ -262,13 +262,13 @@ describe("workflow Git.Push durability", () => { yield* runWorkflowDocument(database, advanced(remote.locator)); const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); expect(remoteBranch(remote, "main")).toBe(head.commit); - const retained = yield* gitHostOutcomes(database); - expect(retained).toHaveLength(1); - const advance = retained[0]?.record; + const saved = yield* gitHostOutcomes(database); + expect(saved).toHaveLength(1); + const advance = saved[0]?.record; expect(advance?.decision).toBe("performed"); expect(advance?.preState).toEqual({ remoteCommit: head.parents[0], @@ -285,7 +285,7 @@ describe("workflow Git.Push durability", () => { expect(subcommands(counting.counters)).not.toContain(command); } expect(yield* gitHostEvents(database)).toHaveLength(1); - expect(yield* gitHostOutcomes(database)).toEqual(retained); + expect(yield* gitHostOutcomes(database)).toEqual(saved); }); }); @@ -295,7 +295,7 @@ describe("workflow Git.Push durability", () => { * Each Push named its request from the checkout as it stood when it ran, so * the first one asked about A and the second about B. Reconstructing either * from the frontier the run ended on would ask the journal a question that - * position never asked — the first Push would arrive at A's retained record + * position never asked — the first Push would arrive at A's saved record * holding B's request — and the run could never be resumed at all. * * The remote is deleted before the continuation, so the two restored records @@ -312,12 +312,12 @@ describe("workflow Git.Push durability", () => { const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); const first = head.parents[0] ?? ""; - const retained = yield* gitHostOutcomes(database); - expect(retained).toHaveLength(2); - const sources = retained.map((outcome) => + const saved = yield* gitHostOutcomes(database); + expect(saved).toHaveLength(2); + const sources = saved.map((outcome) => Reflect.get(Object(outcome.record?.result), "sourceCommit"), ); expect(sources).toEqual([first, head.commit]); @@ -333,14 +333,14 @@ describe("workflow Git.Push durability", () => { // Both restored, in order, unchanged — and neither of them asked the // Git host, proved ancestry, fetched or pushed anything again. expect(yield* gitHostEvents(database)).toHaveLength(2); - expect(yield* gitHostOutcomes(database)).toEqual(retained); + expect(yield* gitHostOutcomes(database)).toEqual(saved); for (const command of ["init", "ls-remote", "merge-base", "fetch", "push"]) { expect(subcommands(counting.counters)).not.toContain(command); } // Reading history is not returning to it: the run is still at B. const resumed = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); expect(resumed.commit).toBe(head.commit); expect(committedRoot(path)).toBe(latestRoot(path)); @@ -366,13 +366,13 @@ describe("workflow Git.Push durability", () => { yield* runWorkflowDocument(database, readopted(remote.locator)); const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); - const retained = yield* gitHostOutcomes(database); - expect(retained).toHaveLength(2); - const created = retained[0]?.record; - const adopted = retained[1]?.record; + const saved = yield* gitHostOutcomes(database); + expect(saved).toHaveLength(2); + const created = saved[0]?.record; + const adopted = saved[1]?.record; expect(created?.decision).toBe("performed"); expect(created?.preState).toEqual({ remoteCommit: null }); expect(adopted?.decision).toBe("adopted"); @@ -387,18 +387,18 @@ describe("workflow Git.Push durability", () => { for (const command of ["init", "ls-remote", "merge-base", "fetch", "push"]) { expect(subcommands(counting.counters)).not.toContain(command); } - expect(yield* gitHostOutcomes(database)).toEqual(retained); + expect(yield* gitHostOutcomes(database)).toEqual(saved); }); }); /** - * A completed push derives and parses its retained request, and asks the + * A completed push derives and parses its saved request, and asks the * object source for nothing. * * Containment is the live provider's first act, so a replay that performed it * anyway would fail here — the object graph is left in a state no push may * read from before the replay runs. It succeeds, which proves the intended - * isolation: the shared engine hands back the retained record without + * isolation: the shared engine hands back the saved record without * reaching a provider, so no graph is walked, no control repository is built * and no Git runs for the push. */ @@ -416,7 +416,7 @@ describe("workflow Git.Push durability", () => { const published = yield* gitHostOutcomes(database); expect(published[0]?.status).toBe("ok"); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const checkout = repository?.record.checkoutPath ?? ""; yield* writeCheckoutFile( database, @@ -439,7 +439,7 @@ describe("workflow Git.Push durability", () => { }); /** - * Every way a retained record can stop describing the invocation it was + * Every way a saved record can stop describing the invocation it was * recorded for. * * Shape is the cheap half. The rest is meaning: a remote that is not origin, @@ -556,7 +556,7 @@ describe("workflow Git.Push durability", () => { ]; for (const { name, damage } of DAMAGE) { - it(`fails a replay whose retained record ${name}`, function* () { + it(`fails a replay whose saved record ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -658,7 +658,7 @@ describe("workflow Git.Push durability", () => { ).toBe("adopted"); }); - it("fails a replay whose retained record answers a different request", function* () { + it("fails a replay whose saved record answers a different request", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -830,7 +830,7 @@ describe("workflow Git.Push durability", () => { const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); expect(remoteBranch(remote, BRANCH)).toBe(head.commit); @@ -889,7 +889,7 @@ describe("workflow Git.Push durability", () => { const head = yield* headCommit( database, - (yield* retainedRepositories(database))[0]?.record.checkoutPath ?? "", + (yield* savedRepositories(database))[0]?.record.checkoutPath ?? "", ); expect(remoteBranch(remote, BRANCH)).toBe(head.commit); expect(counting.counters.effects).toContain("git:push"); @@ -897,7 +897,7 @@ describe("workflow Git.Push durability", () => { }); }); - it("moves no retained row and publishes no Workspace root", function* () { + it("moves no saved row and publishes no Workspace root", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -912,9 +912,9 @@ describe("workflow Git.Push durability", () => { expect(committedRoot(path)).toBe(latestRoot(path)); expect(yield* gitHostEvents(database)).toHaveLength(1); - // The retained Repository row is the one its creation effect journaled: + // The saved Repository row is the one its creation effect journaled: // a Push retains no host path, no materialization and no current branch. - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const created = yield* compositionEvents(database); const journaled = Reflect.get( Object(Reflect.get(Object(Reflect.get(created[0] ?? {}, "result")), "value")), @@ -939,7 +939,7 @@ describe("workflow Git.Push durability", () => { }); /** - * Retained state that stopped agreeing with the identity naming it. + * Saved state that stopped agreeing with the identity naming it. * * Damaged between the Repository's attachment and the push, so the condition * is the one this operation's own selection finds rather than one attachment @@ -948,7 +948,7 @@ describe("workflow Git.Push durability", () => { * run cannot have written — fatal, unrepaired, and reached before a host path * is joined or a remote exists in the story. */ - it("fails on retained state that no longer agrees, without reaching a remote", function* () { + it("fails on saved state that no longer agrees, without reaching a remote", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -1001,12 +1001,12 @@ describe("workflow Git.Push durability", () => { }); /** - * The whole authored vocabulary, retained, and replayed cold. + * The whole authored vocabulary, saved, and replayed cold. * * ``, `` and the four `` elements reach their * provider through `Repository` and `Git`, and a consumer replaces those two * names. Middleware that observes and delegates is what shows the components - * arrive there: an element still routed through an operation the retained + * arrive there: an element still routed through an operation the saved * provider no longer answers would refuse rather than be seen, and one routed * through a second identity would perform the work unobserved. * @@ -1085,7 +1085,7 @@ describe("the authored Git vocabulary under a workflow run", () => { // The normalized results are unchanged: `` still renders the // commit it made, and that commit is what the branch holds at the remote. - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); const head = yield* headCommit(database, worktree?.checkoutPath ?? ""); expect(live).toContain(`commit ${head.commit}`); expect(remoteBranch(remote, FULL_BRANCH)).toBe(head.commit); diff --git a/packages/git/tests/git-push.test.ts b/packages/git/tests/git-push.test.ts index 160a18f9b..afef4f5b1 100644 --- a/packages/git/tests/git-push.test.ts +++ b/packages/git/tests/git-push.test.ts @@ -5,7 +5,7 @@ * remote and a real `git`, because the test checks two places at once: what the * other end of the transport holds afterwards, and what this run's journal says * it published. Rendered text proves neither — a push renders nothing — so - * every test here reads the remote's refs and the retained reconciliation + * every test here reads the remote's refs and the saved reconciliation * record. * * Nothing here reaches a network. A remote is a bare repository in a temporary @@ -60,8 +60,8 @@ import { headCommit, inCheckout, raised, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runWorkflowDocument, subcommands, survivingRoots, @@ -200,7 +200,7 @@ const FORGED: RepositorySelection = Object.freeze({ }); /** - * A test-owned component that writes bytes into the retained checkout. + * A test-owned component that writes bytes into the saved checkout. * * Reaches past every component on purpose, and mid-document on purpose: what it * plants has to be there before `` expands, and `` renders its @@ -239,7 +239,7 @@ function plant( } /** - * A test-owned component that edits the retained checkout's object graph. + * A test-owned component that edits the saved checkout's object graph. * * Mid-document on purpose: what it writes has to be there when `` * expands, and only a fixture can plant a symbolic link or an alternates file @@ -292,7 +292,7 @@ function isProviderError(value: unknown): value is GitCompositionProviderError { /** What the run retains for its one Repository, so a suite can read a checkout. */ function* checkoutPath(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { throw new Error("the run retains no Repository"); } @@ -326,7 +326,7 @@ describe("workflow Git.Push", () => { expect(record?.decision).toBe("performed"); expect(record?.preState).toEqual({ remoteCommit: null }); expect(record?.observations).toEqual({ remoteCommit: head.commit }); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const identity = repository?.record; expect(record?.result).toEqual({ repository: { @@ -393,7 +393,7 @@ describe("workflow Git.Push", () => { ), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); const head = yield* headCommit(database, worktree?.checkoutPath ?? ""); expect(head.branch).toBe("feature/new"); expect(remoteBranch(remote, "feature/new")).toBe(head.commit); @@ -486,7 +486,7 @@ describe("workflow Git.Push", () => { // parent of the one that replaced it. expect(head.parents).not.toContain(published1); - // The predecessor is retained, and so is the fact that made publishing + // The predecessor is saved, and so is the fact that made publishing // over it an advance rather than a replacement. expect(second?.decision).toBe("performed"); expect(second?.preState).toEqual({ remoteCommit: published1, relation: "ancestor" }); @@ -869,7 +869,7 @@ describe("workflow Git.Push", () => { }); }); - it("fails when the working directory is inside no retained checkout", function* () { + it("fails when the working directory is inside no saved checkout", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); @@ -1013,7 +1013,7 @@ describe("workflow Git.Push", () => { describe("workflow Git.Push containment", () => { /** - * A checkout's own configuration is retained document data, not an + * A checkout's own configuration is saved document data, not an * instruction to the run. * * Every setting planted here is one that would change where this push goes, @@ -1026,7 +1026,7 @@ describe("workflow Git.Push containment", () => { * pushed from the ordinary way, and that one is redirected and refused. A * planted configuration Git would have ignored anyway would prove nothing. */ - it("publishes to the retained locator despite a hostile checkout configuration", function* () { + it("publishes to the saved locator despite a hostile checkout configuration", function* () { const root = yield* useStorageRoot(); const witness = yield* useTempDirectory("xmd-push-hook-"); const sentinel = `${witness}/pre-push-ran`; @@ -1067,7 +1067,7 @@ describe("workflow Git.Push containment", () => { const checkout = yield* checkoutPath(database); const head = yield* headCommit(database, checkout); - // The retained locator decided the destination, not `pushurl` and not the + // The saved locator decided the destination, not `pushurl` and not the // rewrite: the real remote has the branch and the decoy has nothing. expect(remoteBranch(remote, BRANCH)).toBe(head.commit); expect(remoteBranch(decoy, BRANCH)).toBe(undefined); @@ -1101,7 +1101,7 @@ describe("workflow Git.Push containment", () => { * What public Git-host middleware is shown, and what it can do with it. * * One frozen routing request describing the detached ask, and nothing that is - * a capability. The object source, the retained locator, the control + * a capability. The object source, the saved locator, the control * repository and every host path stay in the provider's own closure — so a * handler cannot redirect the push, and a value it returns cannot become the * effect's outcome. diff --git a/packages/git/tests/git-switch-durability.test.ts b/packages/git/tests/git-switch-durability.test.ts index 008a94fbf..e6b3001fc 100644 --- a/packages/git/tests/git-switch-durability.test.ts +++ b/packages/git/tests/git-switch-durability.test.ts @@ -2,14 +2,14 @@ * Tier WF — what a `` retains, replays and refuses to guess. * * These tests inspect the run's database rather than a checkout. A replay must - * move no branch and append no second transition; a retained result that no + * move no branch and append no second transition; a saved result that no * longer parses must stop the run rather than be read around; a cancelled * switch must leave the frontier, the materialization and the journal exactly - * as it found them; and the retained rows a switch runs against must be the + * as it found them; and the saved rows a switch runs against must be the * same rows afterwards, because creation identity does not move when HEAD does. * * Every replay happens after the remote is deleted, so what continues a run is - * what the run retained or nothing does. + * what the run saved or nothing does. */ import { describe, it } from "@executablemd/test-support/bdd"; @@ -57,7 +57,7 @@ import { gitEvents, gitOutcomes, raised, - retainedRepositories, + savedRepositories, runDocument, subcommands, survivingRoots, @@ -145,7 +145,7 @@ function tableNames(path: string): string[] { } /** - * Damage the one retained switch result, and refuse to continue if it damaged + * Damage the one saved switch result, and refuse to continue if it damaged * none. * * A tamper that matched nothing leaves the run healthy, and a regression built @@ -272,7 +272,7 @@ describe("workflow Git.Switch durability", () => { }); /** - * Every way a retained result can stop describing the invocation it was + * Every way a saved result can stop describing the invocation it was * recorded for. * * Shape is the cheap half. The rest is meaning: an object id that is not one, @@ -340,7 +340,7 @@ describe("workflow Git.Switch durability", () => { ]; for (const { name, damage } of DAMAGE) { - it(`fails a replay whose retained result ${name}`, function* () { + it(`fails a replay whose saved result ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -365,7 +365,7 @@ describe("workflow Git.Switch durability", () => { } /** - * A retained Worktree identity is a name *and* the place that name is given. + * A saved Worktree identity is a name *and* the place that name is given. * * Placement is a function of identity, so the two travel together and have to * still be the pair placement produces. A result naming one Worktree at @@ -388,7 +388,7 @@ describe("workflow Git.Switch durability", () => { ]; for (const { name, damage } of WORKTREE_DAMAGE) { - it(`fails a replay whose retained result ${name}`, function* () { + it(`fails a replay whose saved result ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -421,7 +421,7 @@ describe("workflow Git.Switch durability", () => { yield* withStorage(root, function* () { const database = yield* createRun(); // Switching to the branch the checkout is already on moves nothing, so a - // retained index digest that changed across it is describing a transition + // saved index digest that changed across it is describing a transition // this component does not make. expect(String(yield* runDocument(database, unchangedSource(remote.locator)))).toContain( "after: main", @@ -548,7 +548,7 @@ describe("workflow Git.Switch durability", () => { // The Repository before it committed; the switch did not, and published // no failed result either — a cancelled effect is not an outcome. - expect(yield* retainedRepositories(database)).toHaveLength(1); + expect(yield* savedRepositories(database)).toHaveLength(1); expect(yield* gitEvents(database)).toHaveLength(0); expect(committedRoot(path)).toBe(latestRoot(path)); expect(yield* survivingRoots({ commands: [], roots, effects: [], attachments: [] })).toEqual( @@ -573,13 +573,13 @@ describe("workflow Git.Switch durability", () => { yield* runDocument(switched, source(remote.locator), countingOptions(counting)); // A switch adds nothing to the schema: the branch it moved lives in the - // retained checkout, not in a column. + // saved checkout, not in a column. expect(tableNames(runPath(root, "switched"))).toEqual(tableNames(runPath(root, "untouched"))); // And it moves nothing in the row. Creation identity is what the row // holds, and the checkout is on another branch by now. - const [before] = yield* retainedRepositories(untouched); - const [after] = yield* retainedRepositories(switched); + const [before] = yield* savedRepositories(untouched); + const [after] = yield* savedRepositories(switched); expect(after?.record).toEqual(before?.record); expect(after?.record.primaryBranch).toBe("main"); expect(after?.record.creationCommit).toBe(remote.heads.get("main")); @@ -590,11 +590,11 @@ describe("workflow Git.Switch durability", () => { // Nothing the host owns reaches the journal: not the disposable // materialization the operation ran in, and not the directory it lived in. const [outcome] = yield* gitOutcomes(switched); - const retained = JSON.stringify(outcome?.record); + const saved = JSON.stringify(outcome?.record); for (const materialization of counting.counters.roots) { - expect(retained).not.toContain(materialization); + expect(saved).not.toContain(materialization); } - expect(retained).not.toContain(tmpdir()); + expect(saved).not.toContain(tmpdir()); }); }); }); diff --git a/packages/git/tests/git-switch.test.ts b/packages/git/tests/git-switch.test.ts index bcd58e10b..3a494ebab 100644 --- a/packages/git/tests/git-switch.test.ts +++ b/packages/git/tests/git-switch.test.ts @@ -60,8 +60,8 @@ import { gitOutcomes, physicalGitApiCopy, raised, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, runWorkflowDocument, subcommands, @@ -162,7 +162,7 @@ function runForged( } /** - * What a retained result is read back for. + * What a saved result is read back for. * * A result is parsed for one request, so a suite that wants to read one has to * say which request it is reading it for — which is the point: the parse is a @@ -175,11 +175,11 @@ function* expectation( base?: string, within = "", ): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { - throw new Error("the run retained no repository to read a result against"); + throw new Error("the run saved no repository to read a result against"); } - const [worktree] = yield* retainedWorktrees(database, repository.record.name); + const [worktree] = yield* savedWorktrees(database, repository.record.name); return { repository: repository.record, workingDirectory: @@ -198,7 +198,7 @@ function document(locator: string, ...lines: string[]): string { * A working directory inside the selected checkout that is not a directory. * * The subject of the case below is the Git provider's own admission check: a - * contextual working directory the retained checkout does not actually hold is + * contextual working directory the saved checkout does not actually hold is * refused, before the provider spawns anything. Reaching that state needs a cwd * that names something which is not there. * @@ -261,23 +261,20 @@ describe("workflow Git.Switch", () => { const [outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitSwitchResult( - outcome?.record, - yield* expectation(database, "release"), - ); - expect(retained?.checkout.repositoryName).toBe("project"); - expect(retained?.checkout.worktreeName).toBe(null); - expect(retained?.requestedBranch).toBe("release"); - expect(retained?.resolvedBranch).toBe("release"); - expect(retained?.requestedBase).toBe(null); + const saved = parseGitSwitchResult(outcome?.record, yield* expectation(database, "release")); + expect(saved?.checkout.repositoryName).toBe("project"); + expect(saved?.checkout.worktreeName).toBe(null); + expect(saved?.requestedBranch).toBe("release"); + expect(saved?.resolvedBranch).toBe("release"); + expect(saved?.requestedBase).toBe(null); // The branch already existed, so nothing was created from a base. - expect(retained?.resolvedBase).toBe(null); - expect(retained?.before.branch).toBe("main"); - expect(retained?.before.commit).toBe(remote.heads.get("main")); - expect(retained?.after.commit).toBe(remote.heads.get("release")); - expect(retained?.before.headTree).not.toBe(retained?.after.headTree); + expect(saved?.resolvedBase).toBe(null); + expect(saved?.before.branch).toBe("main"); + expect(saved?.before.commit).toBe(remote.heads.get("main")); + expect(saved?.after.commit).toBe(remote.heads.get("release")); + expect(saved?.before.headTree).not.toBe(saved?.after.headTree); // A clean checkout stages nothing, so the index describes what HEAD does. - expect(retained?.after.indexTree).toBe(retained?.after.headTree); + expect(saved?.after.indexTree).toBe(saved?.after.headTree); expect(yield* survivingRoots(counting.counters)).toEqual([]); }); }); @@ -301,14 +298,14 @@ describe("workflow Git.Switch", () => { expect(String(output)).toContain("after: main"); const [outcome] = yield* gitOutcomes(database); - const retained = parseGitSwitchResult( + const saved = parseGitSwitchResult( outcome?.record, yield* expectation(database, "feature/new"), ); - expect(retained?.resolvedBranch).toBe("feature/new"); - expect(retained?.requestedBase).toBe(null); - expect(retained?.resolvedBase).toBe(remote.heads.get("main")); - expect(retained?.after.commit).toBe(remote.heads.get("main")); + expect(saved?.resolvedBranch).toBe("feature/new"); + expect(saved?.requestedBase).toBe(null); + expect(saved?.resolvedBase).toBe(remote.heads.get("main")); + expect(saved?.after.commit).toBe(remote.heads.get("main")); }); }); @@ -331,13 +328,13 @@ describe("workflow Git.Switch", () => { expect(String(output)).toContain("after: release"); const [outcome] = yield* gitOutcomes(database); - const retained = parseGitSwitchResult( + const saved = parseGitSwitchResult( outcome?.record, yield* expectation(database, "feature/new", "release"), ); - expect(retained?.requestedBase).toBe("release"); - expect(retained?.resolvedBase).toBe(remote.heads.get("release")); - expect(retained?.after.commit).toBe(remote.heads.get("release")); + expect(saved?.requestedBase).toBe("release"); + expect(saved?.resolvedBase).toBe(remote.heads.get("release")); + expect(saved?.after.commit).toBe(remote.heads.get("release")); }); }); @@ -371,13 +368,13 @@ describe("workflow Git.Switch", () => { expect(rendered).toContain("outside: main"); const [outcome] = yield* gitOutcomes(database); - const retained = parseGitSwitchResult( + const saved = parseGitSwitchResult( outcome?.record, yield* expectation(database, "release", undefined, "/worktrees/"), ); - expect(retained?.checkout.worktreeName).toBe("implementation"); - expect(retained?.before.branch).toBe("feature/new"); - expect(retained?.after.branch).toBe("release"); + expect(saved?.checkout.worktreeName).toBe("implementation"); + expect(saved?.before.branch).toBe("feature/new"); + expect(saved?.after.branch).toBe("release"); }); }); @@ -405,19 +402,16 @@ describe("workflow Git.Switch", () => { const rendered = String(output); expect(rendered).toContain("which: release"); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); expect( yield* workspaceText(database, `${repository?.record.checkoutPath}/shared.txt`), ).toContain("carried"); const [outcome] = yield* gitOutcomes(database); - const retained = parseGitSwitchResult( - outcome?.record, - yield* expectation(database, "release"), - ); + const saved = parseGitSwitchResult(outcome?.record, yield* expectation(database, "release")); // A modified working tree is not a staged one: the index still describes // HEAD on both sides of the switch. - expect(retained?.before.indexTree).toBe(retained?.before.headTree); - expect(retained?.after.indexTree).toBe(retained?.after.headTree); + expect(saved?.before.indexTree).toBe(saved?.before.headTree); + expect(saved?.after.indexTree).toBe(saved?.after.headTree); }); }); @@ -454,7 +448,7 @@ describe("workflow Git.Switch", () => { const [outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("err"); expect(committedRoot(path)).toBe(latestRoot(path)); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const held = yield* workspaceText(database, `${repository?.record.checkoutPath}/which.txt`); expect(held).toContain("local"); expect(held).not.toContain("release"); @@ -509,17 +503,17 @@ describe("workflow Git.Switch selection", () => { ); // A directory inside a checkout is where Git runs, not what it runs on: - // the whole checkout moved, and the retained identity is the Repository's. + // the whole checkout moved, and the saved identity is the Repository's. expect(String(output)).toContain("after: release"); const [outcome] = yield* gitOutcomes(database); - const [repository] = yield* retainedRepositories(database); - const retained = parseGitSwitchResult( + const [repository] = yield* savedRepositories(database); + const saved = parseGitSwitchResult( outcome?.record, yield* expectation(database, "release", undefined, "/nested"), ); - expect(retained?.checkout.worktreeName).toBe(null); - expect(retained?.checkout.checkoutPath).toBe(repository?.record.checkoutPath); - expect(retained?.after.branch).toBe("release"); + expect(saved?.checkout.worktreeName).toBe(null); + expect(saved?.checkout.checkoutPath).toBe(repository?.record.checkoutPath); + expect(saved?.after.branch).toBe("release"); }); }); @@ -552,13 +546,13 @@ describe("workflow Git.Switch selection", () => { expect(rendered).toContain("inside: release"); expect(rendered).toContain("outside: main"); const [outcome] = yield* gitOutcomes(database); - const [worktree] = yield* retainedWorktrees(database, "project"); - const retained = parseGitSwitchResult( + const [worktree] = yield* savedWorktrees(database, "project"); + const saved = parseGitSwitchResult( outcome?.record, yield* expectation(database, "release", undefined, "/worktrees/"), ); - expect(retained?.checkout.worktreeName).toBe("implementation"); - expect(retained?.checkout.checkoutPath).toBe(worktree?.checkoutPath); + expect(saved?.checkout.worktreeName).toBe("implementation"); + expect(saved?.checkout.checkoutPath).toBe(worktree?.checkoutPath); }); }); @@ -629,18 +623,18 @@ describe("workflow Git.Switch selection", () => { ].join("\n"); yield* withStorage(root, function* () { - const unretainedRun = yield* createRun({ runId: "ghost" }); + const unsavedRun = yield* createRun({ runId: "ghost" }); const counting = countingHost(); - const unretained = yield* raised( - runForged(unretainedRun, FORGED, source, countingOptions(counting)), + const unsaved = yield* raised( + runForged(unsavedRun, FORGED, source, countingOptions(counting)), ); - expect(causedBy(unretained, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); - expect(causedBy(unretained, isGitFailure)).toBe(undefined); + expect(causedBy(unsaved, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); + expect(causedBy(unsaved, isGitFailure)).toBe(undefined); // Not an outcome: nothing about it is in this run's history. - expect(yield* gitEvents(unretainedRun)).toHaveLength(0); + expect(yield* gitEvents(unsavedRun)).toHaveLength(0); // The second context names a Repository this run really retains, and - // carries a record that is not the retained one. A name can be looked up; + // carries a record that is not the saved one. A name can be looked up; // the record is compared, so the substitution is what fails. const substitutedRun = yield* createRun({ runId: "substituted" }); const substituted = yield* raised( @@ -652,26 +646,26 @@ describe("workflow Git.Switch selection", () => { ), ); expect(causedBy(substituted, isAdmissionFailure)).toBeInstanceOf(GitOperationAdmissionError); - expect(yield* retainedRepositories(substitutedRun)).toHaveLength(1); + expect(yield* savedRepositories(substitutedRun)).toHaveLength(1); expect(yield* gitEvents(substitutedRun)).toHaveLength(0); - // And a context carrying the retained Repository's own facts, exactly, + // And a context carrying the saved Repository's own facts, exactly, // still admits nothing: a selection is what this provider minted, // not what a value says about itself. The identity here is the one the - // first run retained, which the same fixture retains again here — + // first run saved, which the same fixture retains again here — // creation identity is a function of the name, the url and the base. - const [retained] = yield* retainedRepositories(unretainedRun); + const [saved] = yield* savedRepositories(unsavedRun); const exactRun = yield* createRun({ runId: "exact" }); const exact = yield* raised( runForged( exactRun, - retained === undefined + saved === undefined ? FORGED : { ...FORGED, - name: retained.record.name, - identity: filteredRepositoryIdentity(retained.record), - checkoutPath: retained.record.checkoutPath, + name: saved.record.name, + identity: filteredRepositoryIdentity(saved.record), + checkoutPath: saved.record.checkoutPath, }, source, countingOptions(counting), @@ -850,7 +844,7 @@ describe("workflow Git.Switch failure kinds", () => { ); expect(causedBy(failure, isGitFailure)).toBe(undefined); expect(yield* gitEvents(database)).toHaveLength(0); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); expect(yield* workspaceText(database, `${repository?.record.checkoutPath}/which.txt`)).toBe( "main\n", ); @@ -885,10 +879,10 @@ describe("workflow Git.Switch failure kinds", () => { expect(failure).not.toBe(undefined); expect(causedBy(failure, isGitFailure)).toBe(undefined); - // Nothing was committed for the effect that could not be retained, so + // Nothing was committed for the effect that could not be saved, so // the checkout is still where the Repository left it. expect(yield* gitEvents(database)).toHaveLength(0); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); expect(yield* workspaceText(database, `${repository?.record.checkoutPath}/which.txt`)).toBe( "main\n", ); @@ -924,7 +918,7 @@ describe("workflow Git.Switch failure kinds", () => { * identity: two copies of `git-api.ts` create two different `Api` objects, and * both have to reach the one provider a host installed. What a copy does not get * is admitted — its request is read by the same provider, against the same - * retained rows, and a request naming something this run does not hold is + * saved rows, and a request naming something this run does not hold is * refused exactly as the packaged component's would be. */ describe("workflow Git composition routing", () => { @@ -968,7 +962,7 @@ describe("workflow Git composition routing", () => { expect(outcomes.map((outcome) => outcome.status)).toEqual(["ok"]); // What the copy does not get is admission. Its request is read against the - // same retained rows, and one naming something this run does not hold + // same saved rows, and one naming something this run does not hold // fails the run rather than being answered. const forgedRun = yield* createRun({ runId: "loaded-copy-forged" }); const failure = yield* raised( @@ -1139,28 +1133,25 @@ describe("workflow Git.Switch request ownership", () => { expect(String(output)).toContain("after: release"); expect(counting.counters.effects).toEqual(["repository:project", "git:switch"]); - // What it retained says the same thing, and is read back for the request + // What it saved says the same thing, and is read back for the request // that was admitted rather than for the one the caller holds. const [outcome] = yield* gitOutcomes(database); expect(outcome?.status).toBe("ok"); - const retained = parseGitSwitchResult( - outcome?.record, - yield* expectation(database, "release"), - ); - expect(retained?.checkout.repositoryName).toBe("project"); - expect(retained?.checkout.worktreeName).toBe(null); - expect(retained?.requestedBranch).toBe("release"); - expect(retained?.requestedBase).toBe(null); - expect(retained?.resolvedBranch).toBe("release"); - expect(retained?.resolvedBase).toBe(null); - expect(retained?.after.commit).toBe(remote.heads.get("release")); + const saved = parseGitSwitchResult(outcome?.record, yield* expectation(database, "release")); + expect(saved?.checkout.repositoryName).toBe("project"); + expect(saved?.checkout.worktreeName).toBe(null); + expect(saved?.requestedBranch).toBe("release"); + expect(saved?.requestedBase).toBe(null); + expect(saved?.resolvedBranch).toBe("release"); + expect(saved?.resolvedBase).toBe(null); + expect(saved?.after.commit).toBe(remote.heads.get("release")); // And so does the effect's own durable identity, which is the admitted // Repository record and the admitted branch, digested. - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const record = repository?.record; if (record === undefined) { - throw new Error("the run retained no repository"); + throw new Error("the run saved no repository"); } const [event] = yield* gitEvents(database); expect(event?.type === "yield" ? event.description.configuration : undefined).toBe( diff --git a/packages/git/tests/github-activation.test.ts b/packages/git/tests/github-activation.test.ts index 6901bb359..e2fe0b850 100644 --- a/packages/git/tests/github-activation.test.ts +++ b/packages/git/tests/github-activation.test.ts @@ -11,7 +11,7 @@ * installation → target recognition → configuration → credential → transport * * Everything driven through the *real* host assembly — the ordinary-run - * profile and a retained workflow run — lives in + * profile and a saved workflow run — lives in * `github-workflow-activation.test.ts`, because assembling either reaches * Workflow's storage adapter and so `node:sqlite`. What is here installs the * adapters directly and is portable across all three runtimes. diff --git a/packages/git/tests/github-workflow-activation.test.ts b/packages/git/tests/github-workflow-activation.test.ts index ed5196a42..be27f2df9 100644 --- a/packages/git/tests/github-workflow-activation.test.ts +++ b/packages/git/tests/github-workflow-activation.test.ts @@ -3,7 +3,7 @@ * * The companion to `github-activation.test.ts`. What is here drives the * assemblies a command actually installs — the ordinary-run profile, and a - * retained source-bundle run through the Workflow host — rather than the + * saved source-bundle run through the Workflow host — rather than the * adapters on their own. Both reach Workflow's storage adapter and so * `node:sqlite`, which Bun does not have and Node keeps behind a flag, so this * file is Deno's alone and the portable ordering cases stay next door. @@ -77,7 +77,7 @@ describe("the bundled profile, assembled", () => { it("installs the whole ordinary-run profile without reading any of it", function* () { // The real assembly this time, not the adapters alone: `useRunComposition` // is what `xmd run` installs, and it brings the repository provider, the - // Git capability, both GitHub adapters and the retained lifecycles with it. + // Git capability, both GitHub adapters and the saved lifecycles with it. // Every outward boundary refuses, so installing any of them wrongly is a // failure here rather than a count checked afterwards. const rendered = yield* scoped(function* () { @@ -113,7 +113,7 @@ describe("the bundled profile, assembled", () => { it("runs a version-2 source-bundle workflow outside Git without reaching GitHub", function* () { // The real lifecycle, not a test-shaped run: a source-bundle creation // begun through the same transitions `xmd workflow start` uses, so what - // executes below is a retained version-2 definition rather than a v1 Git + // executes below is a saved version-2 definition rather than a v1 Git // run that happens to render. const root = yield* useStorageRoot(); yield* withRunHost(root, function* (transitions) { diff --git a/packages/git/tests/issue-markdown.test.ts b/packages/git/tests/issue-markdown.test.ts index 1a8e17db9..be8b75a18 100644 --- a/packages/git/tests/issue-markdown.test.ts +++ b/packages/git/tests/issue-markdown.test.ts @@ -83,7 +83,7 @@ describe("workflow Issue scenarios", () => { throw new Error("the attempt journaled no Issue effect"); } - // The retained effect names the authored `` element in the staged + // The saved effect names the authored `` element in the staged // document itself — diagnostic data beside the identity, computed here // from the checked-in source rather than trusted from the journal. const path = fileURLToPath( diff --git a/packages/git/tests/issue-records.test.ts b/packages/git/tests/issue-records.test.ts index 9fc4b50d4..2a116ada3 100644 --- a/packages/git/tests/issue-records.test.ts +++ b/packages/git/tests/issue-records.test.ts @@ -1,8 +1,8 @@ /** - * Tier WI — what a retained issue record is, and what names it. + * Tier WI — what a saved issue record is, and what names it. * * D1 lives here rather than in a scenario document, and the reason is that the - * proof is compositional. "A changed request cannot consume the result retained + * proof is compositional. "A changed request cannot consume the result saved * for a different one" is three facts stacked: * * 1. the request fingerprint covers every member of the request — the canonical @@ -138,7 +138,7 @@ describe("workflow Issue durable identity", () => { ); }); - it("reads a retained request back as the request it was", function* () { + it("reads a saved request back as the request it was", function* () { for (const request of [REQUEST, READ]) { const round = parseIssueRequest(issueRequestJson(request)); expect(round).toEqual(request); @@ -149,7 +149,7 @@ describe("workflow Issue durable identity", () => { // the other however much of the rest they share. expect(sameIssueRequest(REQUEST, { ...READ, url: TARGET })).toBe(false); - // A retained request carrying the other operation's members is not one this + // A saved request carrying the other operation's members is not one this // boundary wrote: an upsert's `issue` under a read's discriminator would // otherwise parse as a read that quietly dropped what it was asked to file. expect(parseIssueRequest({ ...Object(issueRequestJson(READ)), issue: ISSUE })).toBeUndefined(); @@ -157,7 +157,7 @@ describe("workflow Issue durable identity", () => { parseIssueRequest({ ...Object(issueRequestJson(REQUEST)), operation: "read" }), ).toBeUndefined(); - // A retained request whose tags are out of order is not one this boundary + // A saved request whose tags are out of order is not one this boundary // wrote, and reading it as though it were would let two spellings of one // request answer for each other. expect( diff --git a/packages/git/tests/materialization.test.ts b/packages/git/tests/materialization.test.ts index 5e8f50c98..fa4222848 100644 --- a/packages/git/tests/materialization.test.ts +++ b/packages/git/tests/materialization.test.ts @@ -9,7 +9,7 @@ * * These drive the exporter directly rather than through a component, because * the properties are the exporter's own and no component exists to reach them - * yet. A path that arrives from a retained row is not this provider's word by + * yet. A path that arrives from a saved row is not this provider's word by * the time it is read back, which is why containment is proven from the string * rather than from what the filesystem resolved. */ @@ -52,7 +52,7 @@ function* raised(operation: Operation): Operation { } describe("workflow Workspace materialization", () => { - it("refuses to join a traversal-shaped retained path to a host root", function* () { + it("refuses to join a traversal-shaped saved path to a host root", function* () { const root = yield* useStorageRoot(); const host = yield* useHostRoot(); const escaped = join(host, "..", "escaped-by-traversal"); @@ -61,7 +61,7 @@ describe("workflow Workspace materialization", () => { const database = yield* createRun(); const attempted = yield* transactWorkspaceRoots(database, function* (workspace) { return yield* raised( - exportTree(workspace.filesystem, host, "/../../etc", "a retained checkout"), + exportTree(workspace.filesystem, host, "/../../etc", "a saved checkout"), ); }); expect(attempted.ok).toBe(true); @@ -72,7 +72,7 @@ describe("workflow Workspace materialization", () => { }); }); - it("refuses a retained path that is not absolute", function* () { + it("refuses a saved path that is not absolute", function* () { const root = yield* useStorageRoot(); const host = yield* useHostRoot(); @@ -80,7 +80,7 @@ describe("workflow Workspace materialization", () => { const database = yield* createRun(); const attempted = yield* transactWorkspaceRoots(database, function* (workspace) { return yield* raised( - exportTree(workspace.filesystem, host, "relative/checkout", "a retained checkout"), + exportTree(workspace.filesystem, host, "relative/checkout", "a saved checkout"), ); }); expect(attempted.ok && attempted.value).toBeInstanceOf(RepositoryStaleStateError); @@ -129,7 +129,7 @@ describe("workflow Workspace materialization", () => { yield* files.symlink("/outside/the/workspace", "/tree/escaping.txt"); // Out to the host, then back over a Workspace path of its own. - yield* exportTree(files, host, "/tree", "a retained checkout"); + yield* exportTree(files, host, "/tree", "a saved checkout"); yield* files.remove("/tree", { recursive: true, force: true }); yield* importTree(files, host, "/tree"); @@ -151,7 +151,7 @@ describe("workflow Workspace materialization", () => { expect(carried.value.text).toBe("plain\n"); expect(carried.value.blob).toEqual(binary); expect(carried.value.tool).not.toBe(0); - // A tracked link is content: retained as its target, never resolved, + // A tracked link is content: saved as its target, never resolved, // including one whose target leaves the Workspace entirely. expect(carried.value.link).toBe("text.txt"); expect(carried.value.escaping).toBe("/outside/the/workspace"); @@ -167,7 +167,7 @@ describe("workflow Workspace materialization", () => { const attempted = yield* transactWorkspaceRoots(database, function* (workspace) { yield* workspace.filesystem.writeFile("/not-a-checkout", "a file"); const failure = yield* raised( - exportTree(workspace.filesystem, host, "/not-a-checkout", "a retained checkout"), + exportTree(workspace.filesystem, host, "/not-a-checkout", "a saved checkout"), ); const captured = yield* workspace.capture(); yield* workspace.publish(captured.rootId); diff --git a/packages/git/tests/module-partition.test.ts b/packages/git/tests/module-partition.test.ts index 02a014bea..52b3147cd 100644 --- a/packages/git/tests/module-partition.test.ts +++ b/packages/git/tests/module-partition.test.ts @@ -91,8 +91,8 @@ const PROVIDER_NEUTRAL: readonly string[] = [ "src/issue/records.ts", "src/issue/tracker.ts", "src/plugin.ts", - "src/retained-repository/admission.ts", - "src/retained-repository/records.ts", + "src/saved-repository/admission.ts", + "src/saved-repository/records.ts", ]; /** @@ -158,10 +158,10 @@ const DENO_ADAPTER: readonly string[] = [ "src/deno/composition/subprocess.ts", "src/deno/composition/switch.ts", "src/deno/composition/worktree.ts", - "src/deno/retained-repository/files.ts", - "src/deno/retained-repository/leases.ts", - "src/deno/retained-repository/local.ts", - "src/deno/retained-repository/provider.ts", + "src/deno/saved-repository/files.ts", + "src/deno/saved-repository/leases.ts", + "src/deno/saved-repository/local.ts", + "src/deno/saved-repository/provider.ts", "src/deno/run-composition/ambient.ts", "src/deno/run-composition/checkouts.ts", "src/deno/run-composition/errors.ts", diff --git a/packages/git/tests/plugin.test.ts b/packages/git/tests/plugin.test.ts index 97b7c247d..1a0f91c8d 100644 --- a/packages/git/tests/plugin.test.ts +++ b/packages/git/tests/plugin.test.ts @@ -22,10 +22,10 @@ import { collect, inlineSource, registerComponents } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import type { ExecutionInstallation } from "@executablemd/core/host"; import type { PluginInstallRequest } from "@executablemd/core/api"; -import { retainedWorkflowInstallation } from "../../workflow/src/run.ts"; +import { savedWorkflowInstallation } from "../../workflow/src/run.ts"; import type { WorkflowRun } from "../../workflow/src/run.ts"; import { declaresFor, gitPlugin } from "../src/plugin.ts"; -import { retainedGitHostIdentitiesHere, retainedIssueIdentitiesHere } from "../src/identities.ts"; +import { savedGitHostIdentitiesHere, savedIssueIdentitiesHere } from "../src/identities.ts"; import { GIT_HOST_EFFECT } from "../src/git-host/effect.ts"; import { reconcileGitHostEffect, withGitHostProvider } from "@executablemd/git/api"; import type { GitHostProvider } from "@executablemd/git/api"; @@ -116,12 +116,12 @@ function useEffectComponent(observed: Seen): Operation { // entry held onto here would report what this execution did rather // than what it was handed. observed.gitHost.push( - (yield* retainedGitHostIdentitiesHere())?.map((identity) => ({ + (yield* savedGitHostIdentitiesHere())?.map((identity) => ({ runId: identity.runId, claimed: identity.claimed, })), ); - observed.issue.push(yield* retainedIssueIdentitiesHere()); + observed.issue.push(yield* savedIssueIdentitiesHere()); yield* reconcileGitHostEffect(PUSH); return ""; }, @@ -141,7 +141,7 @@ function gitHostYields(events: DurableEvent[]): DurableEvent[] { } /** - * One document execution, under one retained run and one Plugin contribution. + * One document execution, under one saved run and one Plugin contribution. * * `installation` is the same value in every call a case makes, because that is * the question: a host installs this Plugin once and may run many documents. @@ -180,7 +180,7 @@ function* document(options: { }): Operation { return yield* collect( yield* executeInstalled({ ...inlineSource(SOURCE), stream: options.stream }, [ - retainedWorkflowInstallation(options.run), + savedWorkflowInstallation(options.run), options.installation, ]), ); @@ -369,7 +369,7 @@ describe("the Git Plugin's command profile", () => { }); }); -describe("one Plugin value, one execution's retained identities", () => { +describe("one Plugin value, one execution's saved identities", () => { it("derives each execution's identities from its own history and no other", function* () { // One installation, asked of the Plugin once, and used by every execution // below — which is what a host running several documents under one command @@ -380,7 +380,7 @@ describe("one Plugin value, one execution's retained identities", () => { const historyB = yield* record(SOURCE_B, installation); // Every replay below runs provider-free, so what it consumes is the - // retained record and nothing else, and what it reports is the value its + // saved record and nothing else, and what it reports is the value its // own execution's journal read installed. const first = seen(); yield* execute({ diff --git a/packages/git/tests/pull-request-durability.test.ts b/packages/git/tests/pull-request-durability.test.ts index 667c923b2..b405289c2 100644 --- a/packages/git/tests/pull-request-durability.test.ts +++ b/packages/git/tests/pull-request-durability.test.ts @@ -3,7 +3,7 @@ * * These tests inspect the run's database and the Git host together. A replayed * pull request must reach no host, read no credential and append no second - * record; a retained record that no longer describes the invocation it was + * record; a saved record that no longer describes the invocation it was * recorded for must stop the run rather than be read around; a cancelled * creation must leave the journal exactly as it found it; and what public * routing middleware sees must be the frozen request and nothing that can @@ -89,7 +89,7 @@ function unreachable(): GitHubAccess { } /** - * Damage the one retained pull-request record, and refuse if it damaged none. + * Damage the one saved pull-request record, and refuse if it damaged none. * * A tamper that matched nothing leaves the run healthy, and a regression built * on one passes by replaying an undamaged record. @@ -131,14 +131,14 @@ describe("workflow PullRequest durability", () => { const first = String( yield* runWorkflowDocument(database, published(...pullRequest()), run.options), ); - const retained = yield* gitHostOutcomes(database); - expect(retained).toHaveLength(2); + const saved = yield* gitHostOutcomes(database); + expect(saved).toHaveLength(2); dropRootClose(path); // The pull request this run created is closed and retitled, and the base // branch moves, after the record commits. Creation evidence is what the - // run retained; none of this can change it. + // run saved; none of this can change it. const created = run.store.pullRequests[0]; if (created !== undefined) { created.state = "closed"; @@ -156,10 +156,10 @@ describe("workflow PullRequest durability", () => { // The same evidence, from the journal, with no adapter selected, no // credential read and no request sent. expect(replayed).toBe(first); - expect(yield* gitHostOutcomes(database)).toEqual(retained); + expect(yield* gitHostOutcomes(database)).toEqual(saved); const effects = yield* gitHostEvents(database); expect(effects).toHaveLength(2); - // Each retained effect names its own authored element — the `` + // Each saved effect names its own authored element — the `` // and the `` — as diagnostic data beside the identity the // replay above matched by type and name alone. const document = published(...pullRequest()); @@ -221,7 +221,7 @@ describe("workflow PullRequest durability", () => { ]; for (const { name, damage } of DAMAGE) { - it(`fails a replay whose retained record ${name}`, function* () { + it(`fails a replay whose saved record ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, RUN); @@ -266,7 +266,7 @@ describe("workflow PullRequest durability", () => { ]; for (const { name, damage } of REKEYED) { - it(`fails a replay whose retained record ${name}`, function* () { + it(`fails a replay whose saved record ${name}`, function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, RUN); @@ -292,7 +292,7 @@ describe("workflow PullRequest durability", () => { }); } - it("fails a replay whose retained record answers a different request", function* () { + it("fails a replay whose saved record answers a different request", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, RUN); @@ -362,7 +362,7 @@ describe("workflow PullRequest durability", () => { }); }); - it("fails a replay whose retained update had nothing to perform", function* () { + it("fails a replay whose saved update had nothing to perform", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, RUN); diff --git a/packages/git/tests/pull-request-read.test.ts b/packages/git/tests/pull-request-read.test.ts index 9889ae97b..a916afc59 100644 --- a/packages/git/tests/pull-request-read.test.ts +++ b/packages/git/tests/pull-request-read.test.ts @@ -209,7 +209,7 @@ function* storageDigest(path: string): Operation> { return digests; } -/** Every retained evidence read this run holds. */ +/** Every saved evidence read this run holds. */ function* reads(database: WorkflowRunDatabase): Operation { const events: DurableEvent[] = yield* database.journal.readAll(); return events.filter( @@ -693,7 +693,7 @@ describe("Tier PRR — pull-request evidence", () => { expect((yield* read(unknownStatusState, "checks")).state).toBe("protocol-invalid"); }); - it("PRR9: a pending review with no submission is retained, not refused", function* () { + it("PRR9: a pending review with no submission is saved, not refused", function* () { const host = server({ [REVIEWS]: { body: JSON.stringify([ @@ -1238,9 +1238,9 @@ describe("Tier PRR — pull-request evidence", () => { // Three effects, one per collection: completing one manufactures neither // of the others. - const retained = yield* reads(database); - expect(retained).toHaveLength(3); - const kinds = retained.map((event) => + const saved = yield* reads(database); + expect(saved).toHaveLength(3); + const kinds = saved.map((event) => event.type === "yield" && isJsonObject(event.description.input) ? event.description.input.kind : undefined, @@ -1273,9 +1273,9 @@ describe("Tier PRR — pull-request evidence", () => { reading(host, [SUBJECT_REPO]), ); - const retained = yield* reads(database); - expect(retained).toHaveLength(4); - const inputs = retained.map((event) => + const saved = yield* reads(database); + expect(saved).toHaveLength(4); + const inputs = saved.map((event) => event.type === "yield" && isJsonObject(event.description.input) ? event.description.input : {}, @@ -1296,7 +1296,7 @@ describe("Tier PRR — pull-request evidence", () => { expect(inputs.map((input) => input.provider)).toEqual([null, null, null, "github"]); // And each is a different effect: four reads, four names. - const names = retained.map((event) => + const names = saved.map((event) => event.type === "yield" ? String(event.description.name) : "", ); expect(new Set(names).size).toBe(4); @@ -1354,9 +1354,9 @@ describe("Tier PRR — pull-request evidence", () => { const refused = yield* raised(runWorkflowDocument(database, document, reading(failing))); expect(String(refused)).toContain("did not answer with the complete collection"); - const retained = yield* reads(database); - expect(retained).toHaveLength(1); - const outcome = retained[0]?.result; + const saved = yield* reads(database); + expect(saved).toHaveLength(1); + const outcome = saved[0]?.result; expect(outcome?.status).toBe("err"); expect(outcome && "value" in outcome).toBe(false); }); @@ -1444,9 +1444,9 @@ describe("Tier PRR — pull-request evidence", () => { expect(sessions).toEqual(["open", "close"]); - const retained = yield* reads(database); - expect(retained.every((event) => event.result?.status !== "ok")).toBe(true); - expect(JSON.stringify(retained)).not.toContain('"items"'); + const saved = yield* reads(database); + expect(saved.every((event) => event.result?.status !== "ok")).toBe(true); + expect(JSON.stringify(saved)).not.toContain('"items"'); const working = server({ [REVIEWS]: { body: JSON.stringify([review(1, "APPROVED", "ship it")]) }, diff --git a/packages/git/tests/pull-request-records.test.ts b/packages/git/tests/pull-request-records.test.ts index 6a214bf4e..7f0adb7b8 100644 --- a/packages/git/tests/pull-request-records.test.ts +++ b/packages/git/tests/pull-request-records.test.ts @@ -2,7 +2,7 @@ * Tier U — what a pull-request record is, and what authorizes one. * * Two invariants are checked here and neither needs a Git host, a database or a - * document. The first is that the retained shapes are exact: a record is read + * document. The first is that the saved shapes are exact: a record is read * back for the request that produced it, and a value carrying more, fewer or * different members than the contract declares describes something else. The * second is the admission scan — the closed reading of this run's own journal diff --git a/packages/git/tests/pull-request.test.ts b/packages/git/tests/pull-request.test.ts index a9057f145..301849357 100644 --- a/packages/git/tests/pull-request.test.ts +++ b/packages/git/tests/pull-request.test.ts @@ -35,7 +35,7 @@ import { gitHostOutcomes, headCommit, raised, - retainedRepositories, + savedRepositories, runWorkflowDocument, subcommands, } from "./support/composition.ts"; @@ -69,7 +69,7 @@ function isSelectionFailure(value: unknown): value is GitOperationAdmissionError /** What the run retains for its one Repository, so a suite can read a checkout. */ function* checkoutPath(database: WorkflowRunDatabase): Operation { - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); if (repository === undefined) { throw new Error("the run retains no Repository"); } @@ -113,7 +113,7 @@ describe("workflow PullRequest", () => { const record = Object(yield* pullRequestRecord(database)); expect(record.decision).toBe("performed"); expect(record.preState).toEqual({ pullRequest: null }); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); expect(record.result).toEqual({ repository: { name: "project", @@ -151,7 +151,7 @@ describe("workflow PullRequest", () => { expect(created?.baseRef).toBe("main"); // The base default is the Repository's recorded initial branch, which is // what the first document asserted by writing no base at all. - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); expect(repository?.record.primaryBranch).toBe("main"); }); }); @@ -290,7 +290,7 @@ describe("workflow PullRequest admission", () => { }); }); - it("fails when the working directory is inside no retained checkout", function* () { + it("fails when the working directory is inside no saved checkout", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); @@ -453,7 +453,7 @@ describe("workflow PullRequest admission", () => { const database = yield* createRun(); const counting = countingHost(); const store = gitHubStore({ token: TOKEN }); - // The document names the bare repository directly, so the retained + // The document names the bare repository directly, so the saved // locator is a local path rather than a repository on github.com. const source = [ ``, diff --git a/packages/git/tests/repository-components.test.ts b/packages/git/tests/repository-components.test.ts index 1ad082f8b..fb0664367 100644 --- a/packages/git/tests/repository-components.test.ts +++ b/packages/git/tests/repository-components.test.ts @@ -41,7 +41,7 @@ import { countingHost, countingOptions, raised, - retainedRepositories, + savedRepositories, runDocument, runWorkflowDocument, } from "./support/composition.ts"; @@ -80,12 +80,12 @@ describe("workflow Repository composition", () => { expect(String(output)).toContain("checkout: /repositories/project-"); - const retained = yield* retainedRepositories(database); - expect(retained).toHaveLength(1); - expect(retained[0]?.record.name).toBe("project"); - expect(retained[0]?.record.primaryBranch).toBe("main"); - expect(retained[0]?.record.creationCommit).toBe(remote.heads.get("main")); - expect(retained[0]?.locator).toBe(remote.locator); + const saved = yield* savedRepositories(database); + expect(saved).toHaveLength(1); + expect(saved[0]?.record.name).toBe("project"); + expect(saved[0]?.record.primaryBranch).toBe("main"); + expect(saved[0]?.record.creationCommit).toBe(remote.heads.get("main")); + expect(saved[0]?.locator).toBe(remote.locator); }); }); @@ -245,8 +245,8 @@ describe("workflow Repository composition", () => { ); expect(String(output)).toContain("worktree: /worktrees/"); - const retained = yield* retainedRepositories(database); - expect(retained[0]?.locator).toBe(remote.locator); + const saved = yield* savedRepositories(database); + expect(saved[0]?.locator).toBe(remote.locator); }); }); }); @@ -512,7 +512,7 @@ describe("workflow composition failure policy", () => { // components never offered it as one. expect(failure).not.toBe(undefined); expect(causedBy(failure, isRepositoryRefusal)).toBe(undefined); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); }); }); diff --git a/packages/git/tests/repository-control-plane.test.ts b/packages/git/tests/repository-control-plane.test.ts index ccfec70c2..bc735cfa9 100644 --- a/packages/git/tests/repository-control-plane.test.ts +++ b/packages/git/tests/repository-control-plane.test.ts @@ -44,8 +44,8 @@ import { raised, removeWorkspacePath, replaceWorkspaceTree, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, subcommands, survivingRoots, @@ -106,10 +106,10 @@ describe("workflow Git control-plane containment", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); const checkout = before?.record.checkoutPath ?? ""; - // The checkout root stays a real retained directory. Only the control + // The checkout root stays a real saved directory. Only the control // plane inside it is redirected. yield* linkWorkspacePath(database, `${external}/.git`, `${checkout}/.git`); const externalHead = yield* readTextFile(`${external}/.git/HEAD`); @@ -137,7 +137,7 @@ describe("workflow Git control-plane containment", () => { // The external clone and the run are both exactly as they were. expect(yield* readTextFile(`${external}/.git/HEAD`)).toBe(externalHead); - expect(yield* retainedRepositories(database)).toEqual([before]); + expect(yield* savedRepositories(database)).toEqual([before]); expect(committedRoot(runPath(root, "release-1.4"))).toBe(frontier); expect(committedCompositionEvents(runPath(root, "release-1.4"))).toBe(events); }); @@ -158,7 +158,7 @@ describe("workflow Git control-plane containment", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedWorktrees(database, "project"); + const [before] = yield* savedWorktrees(database, "project"); // A real regular `.git` file, with a traversal-shaped pointer at // compatible external administration. No symbolic link involved. @@ -181,7 +181,7 @@ describe("workflow Git control-plane containment", () => { expect(subcommands(counting.counters)).not.toContain("clone"); expect(subcommands(counting.counters)).not.toContain("worktree"); expect(String(rendered)).not.toContain("child marker"); - expect(yield* retainedWorktrees(database, "project")).toEqual([before]); + expect(yield* savedWorktrees(database, "project")).toEqual([before]); }); }); @@ -199,8 +199,8 @@ describe("workflow Git control-plane containment", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedWorktrees(database, "project"); - const [repository] = yield* retainedRepositories(database); + const [before] = yield* savedWorktrees(database, "project"); + const [repository] = yield* savedRepositories(database); // A canonical Workspace path that is not a slot this repository has. yield* writeWorkspaceFile( @@ -244,7 +244,7 @@ describe("workflow Git control-plane containment", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); yield* linkWorkspacePath(database, external, `${before?.record.checkoutPath}/.git/worktrees`); dropRootClose(runPath(root, "release-1.4")); @@ -321,8 +321,8 @@ describe("workflow Git control-plane completeness", () => { ].join("\n"); yield* runDocument(database, document); - const [repository] = yield* retainedRepositories(database); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [repository] = yield* savedRepositories(database); + const [worktree] = yield* savedWorktrees(database, "project"); // The slot Git wrote, found the way the validator finds it. const pointer = yield* workspaceText(database, `${worktree?.checkoutPath}/.git`); @@ -351,8 +351,8 @@ describe("workflow Git control-plane completeness", () => { expect(String(rendered)).not.toContain("later sibling marker"); // Nothing was repaired and nothing advanced. - expect(yield* retainedRepositories(database)).toEqual([repository]); - expect(yield* retainedWorktrees(database, "project")).toEqual([worktree]); + expect(yield* savedRepositories(database)).toEqual([repository]); + expect(yield* savedWorktrees(database, "project")).toEqual([worktree]); expect(committedRoot(runPath(root, "release-1.4"))).toBe(frontier); expect(committedCompositionEvents(runPath(root, "release-1.4"))).toBe(events); }); diff --git a/packages/git/tests/repository-materialization.test.ts b/packages/git/tests/repository-materialization.test.ts index 9add31ded..95b58e5af 100644 --- a/packages/git/tests/repository-materialization.test.ts +++ b/packages/git/tests/repository-materialization.test.ts @@ -4,7 +4,7 @@ * The materialization is a copy of what the database holds, and every guarantee * this provider offers rests on that: deleting one costs time and nothing else. * These are the rules about the checkout root itself — that it is a real - * retained directory and that the export lands where it was aimed — and the + * saved directory and that the export lands where it was aimed — and the * control that says ordinary tracked symbolic links inside a checkout are still * content. */ @@ -45,8 +45,8 @@ import { raised, removeWorkspacePath, replaceWorkspaceTree, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, subcommands, survivingRoots, @@ -68,7 +68,7 @@ import { } from "./support/replay.ts"; /** - * Native Git may only operate on the provider-owned export of the retained + * Native Git may only operate on the provider-owned export of the saved * checkout. * * The materialization is a copy of what the database holds, and every guarantee @@ -116,7 +116,7 @@ describe("workflow Repository materialization containment", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); yield* linkWorkspacePath(database, external, before?.record.checkoutPath ?? ""); const frontier = committedRoot(runPath(root, "release-1.4")); @@ -145,7 +145,7 @@ describe("workflow Repository materialization containment", () => { // The external clone is untouched, and so is the run. expect((yield* until(stat(external))).isDirectory()).toBe(true); - expect(yield* retainedRepositories(database)).toEqual([before]); + expect(yield* savedRepositories(database)).toEqual([before]); expect(committedRoot(runPath(root, "release-1.4"))).toBe(frontier); expect(committedCompositionEvents(runPath(root, "release-1.4"))).toBe(events); }); @@ -166,7 +166,7 @@ describe("workflow Repository materialization containment", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedWorktrees(database, "project"); + const [before] = yield* savedWorktrees(database, "project"); yield* linkWorkspacePath(database, external, before?.checkoutPath ?? ""); dropRootClose(runPath(root, "release-1.4")); @@ -177,7 +177,7 @@ describe("workflow Repository materialization containment", () => { expect(failure?.message).toContain("is not a directory"); expect(String(rendered)).not.toContain("child marker"); expect(subcommands(counting.counters)).not.toContain("clone"); - expect(yield* retainedWorktrees(database, "project")).toEqual([before]); + expect(yield* savedWorktrees(database, "project")).toEqual([before]); }); }); @@ -189,7 +189,7 @@ describe("workflow Repository materialization containment", () => { const database = yield* createRun(); const document = ``; yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); const checkout = before?.record.checkoutPath ?? ""; const replaced = yield* transactWorkspaceRoots(database, function* (workspace) { @@ -256,7 +256,7 @@ describe("workflow Repository materialization containment", () => { expect(rendered).toContain("child marker"); expect(subcommands(counting.counters)).not.toContain("clone"); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); const link = yield* workspaceEntry(database, `${repository?.record.checkoutPath}/link.txt`); expect(link.kind).toBe("symlink"); expect(link.target).toBe("which.txt"); diff --git a/packages/git/tests/repository-replay.test.ts b/packages/git/tests/repository-replay.test.ts index 4ad7b1f5b..0924f7507 100644 --- a/packages/git/tests/repository-replay.test.ts +++ b/packages/git/tests/repository-replay.test.ts @@ -2,7 +2,7 @@ * Tier WF — what a `` replays, refuses and leaves behind. * * Every replay here happens after the remote has been deleted, so what - * continues the run is what the run retained or nothing does. The discriminating + * continues the run is what the run saved or nothing does. The discriminating * observations are the Git subcommands a run issued — a replay that clones did * the work again — and the composition events in the journal, which say whether * an effect was appended a second time. Rendered text proves neither: an @@ -45,8 +45,8 @@ import { raised, removeWorkspacePath, replaceWorkspaceTree, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, subcommands, survivingRoots, @@ -80,8 +80,8 @@ describe("workflow Repository replay", () => { const before = (yield* compositionEvents(database)).length; expect(before).toBe(2); - const [repository] = yield* retainedRepositories(database); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [repository] = yield* savedRepositories(database); + const [worktree] = yield* savedWorktrees(database, "project"); dropRootClose(runPath(root, "release-1.4")); // Nothing outside the run's database survives: no remote to clone from, @@ -106,8 +106,8 @@ describe("workflow Repository replay", () => { "worktree:implementation", ]); - const [repositoryAgain] = yield* retainedRepositories(database); - const [worktreeAgain] = yield* retainedWorktrees(database, "project"); + const [repositoryAgain] = yield* savedRepositories(database); + const [worktreeAgain] = yield* savedWorktrees(database, "project"); expect(repositoryAgain?.record).toEqual(repository?.record); expect(worktreeAgain).toEqual(worktree); expect(yield* survivingRoots(counting.counters)).toEqual([]); @@ -139,14 +139,14 @@ describe("workflow Repository replay", () => { }); }); - it("fails closed when the retained checkout is gone", function* () { + it("fails closed when the saved checkout is gone", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); yield* withStorage(root, function* () { const database = yield* createRun(); yield* runDocument(database, source(remote.locator)); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); dropRootClose(runPath(root, "release-1.4")); yield* removeWorkspacePath(database, repository?.record.checkoutPath ?? ""); @@ -166,7 +166,7 @@ describe("workflow Repository replay", () => { }); }); - it("fails closed when the retained record no longer matches", function* () { + it("fails closed when the saved record no longer matches", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); @@ -198,7 +198,7 @@ describe("workflow Repository replay", () => { yield* withStorage(root, function* () { const database = yield* createRun(); yield* runDocument(database, source(remote.locator)); - const [repository] = yield* retainedRepositories(database); + const [repository] = yield* savedRepositories(database); dropRootClose(runPath(root, "release-1.4")); yield* removeWorkspacePath(database, repository?.record.checkoutPath ?? ""); @@ -252,7 +252,7 @@ describe("workflow Repository effect transaction", () => { expect(subcommands(counting.counters)).toContain("clone"); // Nothing crossed the boundary. No metadata, no new root, no event. - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); expect(committedCompositionEvents(path)).toBe(events); expect(committedRoot(path)).toBe(rootBefore); expect(yield* survivingRoots(counting.counters)).toEqual([]); @@ -316,7 +316,7 @@ describe("workflow Repository effect transaction", () => { yield* task.halt(); }); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); expect(committedCompositionEvents(path)).toBe(events); expect(committedRoot(path)).toBe(rootBefore); const surviving: string[] = []; @@ -367,7 +367,7 @@ describe("workflow Repository refusal durability", () => { expect(String(failure)).toContain("does not name a commit"); expect(rendered).not.toContain("later sibling marker"); - // And retained as a failure. This is the claim the plan makes: the run's + // And saved as a failure. This is the claim the plan makes: the run's // own history says the operation did not succeed. const results = yield* compositionResults(database); expect(results).toHaveLength(2); @@ -386,8 +386,8 @@ describe("workflow Repository refusal durability", () => { expect(publishedRoots(path)).toBe(1); expect(committedRoot(path)).toBe(latestRoot(path)); - // And it retained nothing of its own. - expect(yield* retainedRepositories(database)).toHaveLength(1); + // And it saved nothing of its own. + expect(yield* savedRepositories(database)).toHaveLength(1); }); }); @@ -417,12 +417,12 @@ describe("workflow Repository refusal durability", () => { expect(rendered).toContain("later sibling marker"); // Printing changed what the document reports and nothing about what was - // retained: one failed effect, no root published, no row. + // saved: one failed effect, no root published, no row. const results = yield* compositionResults(database); expect(results).toHaveLength(1); expect(results[0]?.status).toBe("err"); expect(publishedRoots(path)).toBe(0); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); }); }); @@ -461,7 +461,7 @@ describe("workflow Repository refusal durability", () => { }); }); -describe("workflow Repository retained integrity", () => { +describe("workflow Repository saved integrity", () => { it("refuses to join a traversal-shaped path to a materialization root", function* () { const root = yield* useStorageRoot(); const host = join(yield* useStorageRoot(), "materialization"); @@ -471,7 +471,7 @@ describe("workflow Repository retained integrity", () => { const escaped = join(host, "..", "..", "etc"); // Straight at the join itself, because this is the one operation that - // turns a retained string into a host location. Every route that reads a + // turns a saved string into a host location. Every route that reads a // damaged row is refused somewhere, but only this proves the refusal is // containment rather than some earlier check happening to notice. const attempted = yield* transactWorkspaceRoots(database, function* (workspace) { @@ -492,7 +492,7 @@ describe("workflow Repository retained integrity", () => { }); }); - it("refuses a traversal-shaped retained path before any host work", function* () { + it("refuses a traversal-shaped saved path before any host work", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const path = runPath(root, "release-1.4"); @@ -501,7 +501,7 @@ describe("workflow Repository retained integrity", () => { const database = yield* createRun(); yield* runDocument(database, ``); - // A retained path that is not the one this identity's placement gives it. + // A saved path that is not the one this identity's placement gives it. // Concatenated with a materialization root it would name a directory // outside the run's own tree entirely. dropRootClose(path); @@ -523,7 +523,7 @@ describe("workflow Repository retained integrity", () => { ), ); - // Damaged retained state, not a refusal: fatal, and `` does + // Damaged saved state, not a refusal: fatal, and `` does // not get to downgrade it. const stale = causedBy(failure, isStale); expect(stale).toBeInstanceOf(RepositoryStaleStateError); @@ -536,7 +536,7 @@ describe("workflow Repository retained integrity", () => { }); }); - it("detects a mutated retained locator before children and later siblings", function* () { + it("detects a mutated saved locator before children and later siblings", function* () { const root = yield* useStorageRoot(); const remote = yield* useBareRemote(REMOTE); const other = yield* useBareRemote({ @@ -588,14 +588,14 @@ describe("workflow Repository retained integrity", () => { /** * Substituting one repository's checkout for another's. * - * The bytes at a retained path are not the run's word by the time they are read + * The bytes at a saved path are not the run's word by the time they are read * back. A valid checkout of a different repository is a perfectly readable Git * repository, so attachment that asked only "can Git read this" accepted one - * identity's retained history against another identity's content: the document + * identity's saved history against another identity's content: the document * carried on restoring recorded reads that no longer described what was there, * and every later live effect ran against the substitute. * - * Each of these builds a real, valid checkout that differs from the retained one + * Each of these builds a real, valid checkout that differs from the saved one * in exactly one dimension of creation identity, so a passing test says which * check caught it rather than that something did. */ @@ -620,7 +620,7 @@ describe("workflow Repository substituted checkouts", () => { ].join("\n"); yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); const checkout = before?.record.checkoutPath ?? ""; const counting = countingHost(); @@ -661,8 +661,8 @@ describe("workflow Repository substituted checkouts", () => { expect(subcommands(counting.counters)).not.toContain("checkout"); expect(subcommands(counting.counters)).not.toContain("worktree"); - // The retained record and the frontier are exactly what they were. - const [after] = yield* retainedRepositories(database); + // The saved record and the frontier are exactly what they were. + const [after] = yield* savedRepositories(database); expect(after).toEqual(before); expect(committedRoot(runPath(root, "release-1.4"))).toBe(frontier); expect(committedCompositionEvents(runPath(root, "release-1.4"))).toBe(events); @@ -682,7 +682,7 @@ describe("workflow Repository substituted checkouts", () => { const database = yield* createRun(); const document = ``; yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); const checkout = before?.record.checkoutPath ?? ""; // B's content, carrying A's locator as its origin. The url matches and the @@ -701,7 +701,7 @@ describe("workflow Repository substituted checkouts", () => { ); expect(failure?.message).toContain("the commit it was created at is not in the checkout"); expect(subcommands(counting.counters)).not.toContain("clone"); - expect(yield* retainedRepositories(database)).toEqual([before]); + expect(yield* savedRepositories(database)).toEqual([before]); }); }); @@ -715,7 +715,7 @@ describe("workflow Repository substituted checkouts", () => { const database = yield* createRun(); const document = ``; yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); expect(before?.record.objectFormat).toBe("sha1"); const checkout = before?.record.checkoutPath ?? ""; @@ -756,7 +756,7 @@ describe("workflow Repository substituted checkouts", () => { const database = yield* createRun(); const document = ``; yield* runDocument(database, document); - const [before] = yield* retainedRepositories(database); + const [before] = yield* savedRepositories(database); const checkout = before?.record.checkoutPath ?? ""; // What #294 will do: the same repository, on another branch, at another diff --git a/packages/git/tests/repository-storage.test.ts b/packages/git/tests/repository-storage.test.ts index c685bdc9c..9927f4e12 100644 --- a/packages/git/tests/repository-storage.test.ts +++ b/packages/git/tests/repository-storage.test.ts @@ -6,8 +6,8 @@ * continued from its own database after the remote is gone, and neither a * stand-in for Git nor a stand-in for DOFS could show that. * - * Two observations do most of the work. The retained rows say what the run - * decided its identity was, and the retained bytes say whether what it decided + * Two observations do most of the work. The saved rows say what the run + * decided its identity was, and the saved bytes say whether what it decided * is still there — including the one thing a materialization must never leave * behind, which is its own host path. */ @@ -19,8 +19,8 @@ import { git, moveRemoteBranch, useBareRemote } from "./support/git-remotes.ts"; import { countingHost, countingOptions, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, subcommands, workspaceEntry, @@ -54,11 +54,11 @@ describe("workflow Repository retention", () => { ].join("\n"), ); - const repositories = yield* retainedRepositories(database); + const repositories = yield* savedRepositories(database); expect(repositories.map((entry) => entry.record.name)).toEqual(["api", "sdk"]); - const apiWorktrees = yield* retainedWorktrees(database, "api"); - const sdkWorktrees = yield* retainedWorktrees(database, "sdk"); + const apiWorktrees = yield* savedWorktrees(database, "api"); + const sdkWorktrees = yield* savedWorktrees(database, "sdk"); const paths = [ repositories[0]?.record.checkoutPath, @@ -108,11 +108,11 @@ describe("workflow Repository retention", () => { ``, ); - const [retained] = yield* retainedRepositories(database); - expect(retained?.record.primaryBranch).toBe("trunk"); - expect(retained?.record.requestedBase).toBe(null); - expect(retained?.record.creationCommit).toBe(remote.heads.get("trunk")); - expect(yield* workspaceText(database, `${retained?.record.checkoutPath}/which.txt`)).toBe( + const [saved] = yield* savedRepositories(database); + expect(saved?.record.primaryBranch).toBe("trunk"); + expect(saved?.record.requestedBase).toBe(null); + expect(saved?.record.creationCommit).toBe(remote.heads.get("trunk")); + expect(yield* workspaceText(database, `${saved?.record.checkoutPath}/which.txt`)).toBe( "trunk\n", ); }); @@ -138,17 +138,17 @@ describe("workflow Repository retention", () => { ``, ); - const [retained] = yield* retainedRepositories(database); - expect(retained?.record.primaryBranch).toBe("release"); - expect(retained?.record.requestedBase).toBe("release"); - expect(retained?.record.creationCommit).toBe(remote.heads.get("release")); + const [saved] = yield* savedRepositories(database); + expect(saved?.record.primaryBranch).toBe("release"); + expect(saved?.record.requestedBase).toBe("release"); + expect(saved?.record.creationCommit).toBe(remote.heads.get("release")); // Moving the branch afterwards changes neither the record nor what a // later read of the run finds: resolution happened once. moveRemoteBranch(remote, "release", remote.heads.get("main") ?? ""); - const again = yield* retainedRepositories(database); + const again = yield* savedRepositories(database); expect(again[0]?.record.creationCommit).toBe(remote.heads.get("release")); - expect(yield* workspaceText(database, `${retained?.record.checkoutPath}/which.txt`)).toBe( + expect(yield* workspaceText(database, `${saved?.record.checkoutPath}/which.txt`)).toBe( "release\n", ); }); @@ -170,16 +170,16 @@ describe("workflow Repository retention", () => { ``, ); - const [retained] = yield* retainedRepositories(database); - expect(retained?.record.primaryBranch).toBe("xmd/base"); - expect(retained?.record.creationCommit).toBe(remote.tags.get("v1")); + const [saved] = yield* savedRepositories(database); + expect(saved?.record.primaryBranch).toBe("xmd/base"); + expect(saved?.record.creationCommit).toBe(remote.tags.get("v1")); - // Never detached: the retained HEAD is a symbolic ref to the branch the + // Never detached: the saved HEAD is a symbolic ref to the branch the // provider named, which is what a later Switch, Commit or Push needs. - expect(yield* workspaceText(database, `${retained?.record.checkoutPath}/.git/HEAD`)).toBe( + expect(yield* workspaceText(database, `${saved?.record.checkoutPath}/.git/HEAD`)).toBe( "ref: refs/heads/xmd/base\n", ); - expect(yield* workspaceText(database, `${retained?.record.checkoutPath}/which.txt`)).toBe( + expect(yield* workspaceText(database, `${saved?.record.checkoutPath}/which.txt`)).toBe( "first\n", ); }); @@ -203,7 +203,7 @@ describe("workflow Repository retention", () => { ); expect(String(output)).toContain("does not name a commit"); - expect(yield* retainedRepositories(database)).toHaveLength(0); + expect(yield* savedRepositories(database)).toHaveLength(0); }); }); @@ -234,14 +234,14 @@ describe("workflow Repository retention", () => { ); const rendered = String(output); - const [retained] = yield* retainedRepositories(database); - const path = retained?.record.checkoutPath ?? ""; + const [saved] = yield* savedRepositories(database); + const path = saved?.record.checkoutPath ?? ""; // Compatible reuse addresses the same Repository rather than making a // second one. expect(rendered).toContain(`first: ${path}`); expect(rendered).toContain(`second: ${path}`); - expect(yield* retainedRepositories(database)).toHaveLength(1); + expect(yield* savedRepositories(database)).toHaveLength(1); // A changed locator and a changed base are both incompatible reuse, and // neither repoints the name. @@ -250,7 +250,7 @@ describe("workflow Repository retention", () => { // Both refusals printed and the region carried on, which is what makes // "the name was not repointed" a claim about reuse rather than about a // run that stopped at the first refusal. - expect(yield* retainedRepositories(database)).toHaveLength(1); + expect(yield* savedRepositories(database)).toHaveLength(1); }); }); @@ -278,8 +278,8 @@ describe("workflow Repository retention", () => { ``, ); - const [retained] = yield* retainedRepositories(database); - const checkout = retained?.record.checkoutPath ?? ""; + const [saved] = yield* savedRepositories(database); + const checkout = saved?.record.checkoutPath ?? ""; const tree = yield* workspaceTree(database, checkout); expect(tree.get(`${checkout}/blob.bin`)).toEqual(binary); @@ -356,7 +356,7 @@ describe("workflow Worktree retention", () => { ].join("\n"), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); expect(worktree?.requestedBranch).toBe("feature/new"); expect(worktree?.requestedBase).toBe(null); expect(worktree?.creationCommit).toBe(remote.heads.get("main")); @@ -379,7 +379,7 @@ describe("workflow Worktree retention", () => { ].join("\n"), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); expect(worktree?.requestedBase).toBe("feature/existing"); expect(worktree?.creationCommit).toBe(remote.heads.get("feature/existing")); expect(yield* workspaceText(database, `${worktree?.checkoutPath}/which.txt`)).toBe( @@ -403,7 +403,7 @@ describe("workflow Worktree retention", () => { ].join("\n"), ); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); // The existing branch's commit, not the primary checkout's: the branch // was used rather than recreated at HEAD. expect(worktree?.creationCommit).toBe(remote.heads.get("feature/existing")); @@ -434,7 +434,7 @@ describe("workflow Worktree retention", () => { ); // The first one survives untouched; the refusal added nothing. - const worktrees = yield* retainedWorktrees(database, "project"); + const worktrees = yield* savedWorktrees(database, "project"); expect(worktrees.map((entry) => entry.name)).toEqual(["first"]); }); }); @@ -457,7 +457,7 @@ describe("workflow Worktree retention", () => { ); expect(String(output)).toContain("already checked out by another worktree"); - expect(yield* retainedWorktrees(database, "project")).toHaveLength(0); + expect(yield* savedWorktrees(database, "project")).toHaveLength(0); }); }); @@ -481,10 +481,10 @@ describe("workflow Worktree retention", () => { ); const rendered = String(output); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [worktree] = yield* savedWorktrees(database, "project"); expect(rendered).toContain(`first: ${worktree?.checkoutPath}`); expect(rendered).toContain(`second: ${worktree?.checkoutPath}`); - expect(yield* retainedWorktrees(database, "project")).toHaveLength(1); + expect(yield* savedWorktrees(database, "project")).toHaveLength(1); }); }); @@ -510,9 +510,9 @@ describe("workflow Worktree retention", () => { "that name is already this repository's, for a different branch or base", ); - // The refusal moved nothing: the retained Worktree is the first one, on + // The refusal moved nothing: the saved Worktree is the first one, on // the branch it was created for. - const worktrees = yield* retainedWorktrees(database, "project"); + const worktrees = yield* savedWorktrees(database, "project"); expect(worktrees).toHaveLength(1); expect(worktrees[0]?.requestedBranch).toBe("feature/new"); }); @@ -533,8 +533,8 @@ describe("workflow Worktree retention", () => { ].join("\n"), ); - const [repository] = yield* retainedRepositories(database); - const [worktree] = yield* retainedWorktrees(database, "project"); + const [repository] = yield* savedRepositories(database); + const [worktree] = yield* savedWorktrees(database, "project"); // The two administration files that carry an absolute path hold Workspace // paths, which is what makes the pair relocatable to another host. @@ -568,9 +568,9 @@ describe("workflow composition retention across the run", () => { ); // The document finished and every live materialization is gone. What the - // run holds is what it retained, and no deletion happened on the way out. - const [repository] = yield* retainedRepositories(database); - const [worktree] = yield* retainedWorktrees(database, "project"); + // run holds is what it saved, and no deletion happened on the way out. + const [repository] = yield* savedRepositories(database); + const [worktree] = yield* savedWorktrees(database, "project"); expect(repository?.record.name).toBe("project"); expect(worktree?.name).toBe("implementation"); expect(yield* workspaceText(database, `${repository?.record.checkoutPath}/which.txt`)).toBe( @@ -602,7 +602,7 @@ describe("workflow Repository locator admission", () => { // Two places a token is ordinarily written into a URL. Neither carries // any part of a repository's location for the transports this provider - // admits, and a locator is retained whole — so admitting one would be + // admits, and a locator is saved whole — so admitting one would be // deciding to keep whatever it held. // // The values here are deliberately unremarkable: a realistic secret never @@ -628,8 +628,8 @@ describe("workflow Repository locator admission", () => { expect(output.match(/could not be used as a Git locator/g)).toHaveLength(2); expect(subcommands(counting.counters)).not.toContain("clone"); - // And nothing was written: no row, so nothing retained either part. - expect(yield* retainedRepositories(database)).toHaveLength(0); + // And nothing was written: no row, so nothing saved either part. + expect(yield* savedRepositories(database)).toHaveLength(0); }); }); }); diff --git a/packages/git/tests/retained-repository-lifecycle.test.ts b/packages/git/tests/saved-repository-lifecycle.test.ts similarity index 98% rename from packages/git/tests/retained-repository-lifecycle.test.ts rename to packages/git/tests/saved-repository-lifecycle.test.ts index f84cac4ef..f97f7d8e7 100644 --- a/packages/git/tests/retained-repository-lifecycle.test.ts +++ b/packages/git/tests/saved-repository-lifecycle.test.ts @@ -43,15 +43,15 @@ import { executeInstalled } from "@executablemd/core/host"; import { useAdvisoryLock } from "@executablemd/workflow/deno"; import { denoRepositoryHost } from "../src/deno/composition/host.ts"; import { gitSession } from "../src/deno/composition/git.ts"; -import { useReplRepositoryRecording } from "../src/deno/retained-repository/provider.ts"; -import { admitReplRepositoryHistory } from "../src/retained-repository/admission.ts"; +import { useReplRepositoryRecording } from "../src/deno/saved-repository/provider.ts"; +import { admitReplRepositoryHistory } from "../src/saved-repository/admission.ts"; import { CommonDirectoryLeaseError, useCommonDirectoryLease, -} from "../src/deno/retained-repository/leases.ts"; +} from "../src/deno/saved-repository/leases.ts"; import { commonDirectoryLock } from "../src/deno/run-composition/placement.ts"; import { useCompositionComponents } from "../src/composition/installation.ts"; -import { REPL_REPOSITORY_LOCAL } from "../src/retained-repository/records.ts"; +import { REPL_REPOSITORY_LOCAL } from "../src/saved-repository/records.ts"; /** Let every ready task run, so "reached its wait" is about turns. */ function* settled(turns = 8): Operation { @@ -210,7 +210,7 @@ function entry( output = String( yield* collect( yield* executeInstalled({ path, stream }, [ - { admissions: [(retained) => admitReplRepositoryHistory(retained)] }, + { admissions: [(saved) => admitReplRepositoryHistory(saved)] }, ]), ), ); @@ -374,7 +374,7 @@ describe("an entry's hold on the checkout it is working in", () => { expect(read.refusal).toBe(undefined); expect(reading.reads).toEqual(["held.md"]); expect(String(read.output)).toContain("held"); - // And it is retained, so a continuation answers it without looking. + // And it is saved, so a continuation answers it without looking. expect(localRecords(read.events)).toBe(1); // No descriptor at all: the lease is about one entry at a time changing a // repository, and this one only looked. diff --git a/packages/git/tests/retained-repository-local.test.ts b/packages/git/tests/saved-repository-local.test.ts similarity index 99% rename from packages/git/tests/retained-repository-local.test.ts rename to packages/git/tests/saved-repository-local.test.ts index 13fcfa735..e9794a50b 100644 --- a/packages/git/tests/retained-repository-local.test.ts +++ b/packages/git/tests/saved-repository-local.test.ts @@ -53,15 +53,15 @@ import { executeInstalled } from "@executablemd/core/host"; import { collect, Elicitation } from "@executablemd/core"; import { denoRepositoryHost } from "../src/deno/composition/host.ts"; import { gitSession } from "../src/deno/composition/git.ts"; -import { useReplRepositoryRecording } from "../src/deno/retained-repository/provider.ts"; -import { admitReplRepositoryHistory } from "../src/retained-repository/admission.ts"; +import { useReplRepositoryRecording } from "../src/deno/saved-repository/provider.ts"; +import { admitReplRepositoryHistory } from "../src/saved-repository/admission.ts"; import { useCompositionComponents } from "../src/composition/installation.ts"; import { REPL_REPOSITORY_LOCAL, REPL_REPOSITORY_SELECTION, parseReplLocalRecord, -} from "../src/retained-repository/records.ts"; -import type { ReplLocalValue } from "../src/retained-repository/records.ts"; +} from "../src/saved-repository/records.ts"; +import type { ReplLocalValue } from "../src/saved-repository/records.ts"; interface Fixture { readonly root: string; @@ -207,7 +207,7 @@ function run( yield* executeInstalled({ path, stream, includes: [fixture.root] }, [ // What the Git Plugin contributes for `repl`, attached directly: // this suite assembles no command Plugins. - { admissions: [(retained) => admitReplRepositoryHistory(retained)] }, + { admissions: [(saved) => admitReplRepositoryHistory(saved)] }, ]), ), ); diff --git a/packages/git/tests/retained-repository-records.test.ts b/packages/git/tests/saved-repository-records.test.ts similarity index 98% rename from packages/git/tests/retained-repository-records.test.ts rename to packages/git/tests/saved-repository-records.test.ts index c2f02e9e0..316cd8292 100644 --- a/packages/git/tests/retained-repository-records.test.ts +++ b/packages/git/tests/saved-repository-records.test.ts @@ -30,16 +30,16 @@ import { REPL_REPOSITORY_SELECTION, REPL_REPOSITORY_VERSION, retainableRefusal, -} from "../src/retained-repository/records.ts"; +} from "../src/saved-repository/records.ts"; import type { ReplLocalOperation, ReplLocalRequest, ReplLocalValue, -} from "../src/retained-repository/records.ts"; +} from "../src/saved-repository/records.ts"; import { admitReplRepositoryHistory, ReplRepositoryHistoryError, -} from "../src/retained-repository/admission.ts"; +} from "../src/saved-repository/admission.ts"; const WRITE: ReplLocalRequest = { cwd: "/work", path: "note.md", content: "first" }; diff --git a/packages/git/tests/scenarios/IssueDurability.attempt.stage.md b/packages/git/tests/scenarios/IssueDurability.attempt.stage.md index f78d402c1..f18b26d57 100644 --- a/packages/git/tests/scenarios/IssueDurability.attempt.stage.md +++ b/packages/git/tests/scenarios/IssueDurability.attempt.stage.md @@ -1,7 +1,7 @@ # An attempt that filed an issue and journaled that it had The first attempt of the durability scenario. Nothing goes wrong here: the -tracker accepts the create, the answer gets back, and the result is retained +tracker accepts the create, the answer gets back, and the result is saved under this run. diff --git a/packages/git/tests/scenarios/IssueDurability.test.md b/packages/git/tests/scenarios/IssueDurability.test.md index 3d39081eb..a4a4e19d7 100644 --- a/packages/git/tests/scenarios/IssueDurability.test.md +++ b/packages/git/tests/scenarios/IssueDurability.test.md @@ -17,7 +17,7 @@ run, the same document, the same request. ## The second attempt asks nobody -The replay answers from what the run retained. The evidence is the tracker's +The replay answers from what the run saved. The evidence is the tracker's own request log rather than a claim about the boundary: a replay that had asked would have left a request behind, and it left none. What makes that worth saying is the assertion above it: the first attempt *did* leave requests behind, @@ -41,7 +41,7 @@ in which nobody ever calls anything. -## What the run retained +## What the run saved An upsert retains the normalized request, a reference — a URL — and where this document wrote its ``, and nothing else. The marker in particular is not @@ -50,9 +50,9 @@ recognized by the next one, and the run carries the key it is a digest of. Retaining the marker as well would put a run's internal identity into the document that asked the question. The authored position is the run's own document naming its own element — diagnostic data like `import_component`'s -retained paths, never the provider's or the deployment's. +saved paths, never the provider's or the deployment's. - + diff --git a/packages/git/tests/scenarios/IssueHttp.test.md b/packages/git/tests/scenarios/IssueHttp.test.md index b920ac656..ac17b4898 100644 --- a/packages/git/tests/scenarios/IssueHttp.test.md +++ b/packages/git/tests/scenarios/IssueHttp.test.md @@ -33,7 +33,7 @@ The publish step failed twice in a row on 503. Both requests carried the credential the tracker requires. The header is checked where its value is already known and reported as an answer, so the token appears -neither in this document nor in anything the run retained. +neither in this document nor in anything the run saved. diff --git a/packages/git/tests/scenarios/IssueReadDurability.attempt.stage.md b/packages/git/tests/scenarios/IssueReadDurability.attempt.stage.md index cf4cae302..55b95d791 100644 --- a/packages/git/tests/scenarios/IssueReadDurability.attempt.stage.md +++ b/packages/git/tests/scenarios/IssueReadDurability.attempt.stage.md @@ -16,7 +16,7 @@ here is: a request's identity includes the expansion it was made at. /> On the second attempt the provider boundary refuses everything. A replay that -answered from the retained result does not notice; one that asked would fail +answered from the saved result does not notice; one that asked would fail here rather than quietly making a second request. diff --git a/packages/git/tests/scenarios/IssueReadDurability.test.md b/packages/git/tests/scenarios/IssueReadDurability.test.md index c066657b9..467cd609e 100644 --- a/packages/git/tests/scenarios/IssueReadDurability.test.md +++ b/packages/git/tests/scenarios/IssueReadDurability.test.md @@ -1,7 +1,7 @@ # Reading the same issue twice A read is a durable effect like any other. The second attempt of a run does not -ask again — it answers from what the first retained, and it binds the same +ask again — it answers from what the first saved, and it binds the same `IssueDetails` it bound the first time. @@ -32,9 +32,9 @@ nothing — two independent statements of the same thing. -## What the run retained +## What the run saved -The retained Issue effect holds the normalized request, the normalized public +The saved Issue effect holds the normalized request, the normalized public result and where this document wrote its ``, and none of the things a provider knows that a document must never hold. @@ -44,7 +44,7 @@ loaded; that is the run's business, and no Issue effect either causes it or can prevent it. The authored position is that same run-owned class: it names the run's own document and element, not the provider or the deployment. - + diff --git a/packages/git/tests/scenarios/IssueReadSubstituted.test.md b/packages/git/tests/scenarios/IssueReadSubstituted.test.md index ad0f0c8af..af3d45b25 100644 --- a/packages/git/tests/scenarios/IssueReadSubstituted.test.md +++ b/packages/git/tests/scenarios/IssueReadSubstituted.test.md @@ -1,4 +1,4 @@ -# A substituted answer is not retained, and not replayed +# A substituted answer is not saved, and not replayed Two attempts of one run, both reading issue 1 from a tracker that answers with issue 99. @@ -25,14 +25,14 @@ without asking anybody, and it would restore it under the URL of issue 1. -## And what the run retained was the refusal +## And what the run saved was the refusal The refusal is journaled, so the second attempt restored it instead of asking -again. That is the discriminating fact: had the substituted answer been retained +again. That is the discriminating fact: had the substituted answer been saved as this read's outcome, the replay would have bound issue 99 and succeeded without a request. It refused, with the same words, having sent nothing. - + @@ -45,7 +45,7 @@ without a request. It refused, with the same words, having sent nothing. - + diff --git a/packages/git/tests/support/composition.ts b/packages/git/tests/support/composition.ts index 6a2dba5ee..cda144aef 100644 --- a/packages/git/tests/support/composition.ts +++ b/packages/git/tests/support/composition.ts @@ -18,11 +18,11 @@ import { Git } from "../../src/composition/git-api.ts"; import { collect, execute, INLINE_SOURCE_PATH, inlineSource } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import type { ExecutionInstallation } from "@executablemd/core/host"; -import { retainedWorkflowInstallation } from "../../../workflow/src/run.ts"; +import { savedWorkflowInstallation } from "../../../workflow/src/run.ts"; import { GIT_HOST_EFFECT } from "../../src/git-host/effect.ts"; import type { Json } from "@executablemd/durable-streams"; import type { Yield } from "@executablemd/durable-streams"; -import { readRetainedValues, selectYields } from "@executablemd/test-support/journal"; +import { readSavedValues, selectYields } from "@executablemd/test-support/journal"; import { type GitHostReconciliationRecord, parseGitHostReconciliationRecord, @@ -68,7 +68,7 @@ import { type WorkflowRun, type WorkflowRunRecord } from "@executablemd/workflow * Asked of the Plugin value rather than assembled here, so what these cases * exercise is the same contribution a command gets — one Plugin value, and an * admission that derives this execution's identities from this execution's own - * retained history. + * saved history. */ export function* gitPluginAdmissions(): Operation { const install = gitPlugin.install; @@ -198,7 +198,7 @@ export function runDocument( * The same attachment `runDocument()` makes, plus the workflow-run installation * a Git-host effect needs: external identity is the run and the expansion, and * a document execution that is not associated with a run has no first half of - * that pair. The run's three members come from its own retained record, exactly + * that pair. The run's three members come from its own saved record, exactly * as the CLI supplies them. * * `around` runs inside the attachment and outside the execution, which is where @@ -223,7 +223,7 @@ export function runWorkflowDocument( return yield* around(function* () { return yield* collect( yield* executeInstalled({ ...inlineSource(source), stream: database.journal }, [ - retainedWorkflowInstallation(retainedRunValue(database.record)), + savedWorkflowInstallation(savedRunValue(database.record)), git, ]), ); @@ -258,7 +258,7 @@ export function* gitHostEvents(database: WorkflowRunDatabase): Operation selected.event); } -/** What one Git-host effect settled as, and what it retained when it settled `ok`. */ +/** What one Git-host effect settled as, and what it saved when it settled `ok`. */ export interface GitHostOutcomeRecord { readonly status: string; readonly name: string; @@ -268,7 +268,7 @@ export interface GitHostOutcomeRecord { export function* gitHostOutcomes(database: WorkflowRunDatabase): Operation { const events = yield* database.journal.readAll(); - return readRetainedValues(events, GIT_HOST_EFFECT, parseGitHostReconciliationRecord).map( + return readSavedValues(events, GIT_HOST_EFFECT, parseGitHostReconciliationRecord).map( (outcome) => ({ status: outcome.status, name: outcome.status === "err" ? (outcome.error.name ?? "") : "", @@ -329,7 +329,7 @@ export function* gitEvents(database: WorkflowRunDatabase): Operation { ); } -/** What one Git operation settled as, and what it retained when it settled `ok`. */ +/** What one Git operation settled as, and what it saved when it settled `ok`. */ export interface GitOutcomeRecord { readonly status: string; readonly name: string; @@ -357,9 +357,7 @@ export function* gitOutcomes(database: WorkflowRunDatabase): Operation { +export function* savedRepositories(database: WorkflowRunDatabase): Operation { const read = yield* transactWorkspaceRoots(database, function* (workspace) { return createWorkspaceMetadata(workspace.storage).readRepositories(); }); @@ -369,7 +367,7 @@ export function* retainedRepositories( return read.value; } -export function* retainedWorktrees( +export function* savedWorktrees( database: WorkflowRunDatabase, repositoryName: string, ): Operation { @@ -450,10 +448,10 @@ export function* workspaceTree( } /** - * What the index of one retained checkout holds staged, as native Git reads it. + * What the index of one saved checkout holds staged, as native Git reads it. * * The direct observation staging verification needs. Index trees say *that* - * something changed; this says *what*, by exporting the retained family into a + * something changed; this says *what*, by exporting the saved family into a * disposable host tree — the same way the provider does — and asking Git. */ export function* stagedPaths( @@ -468,7 +466,7 @@ export function* stagedPaths( }); } -/** Run commands with one retained checkout exported the way the provider does. */ +/** Run commands with one saved checkout exported the way the provider does. */ export function* inCheckout( database: WorkflowRunDatabase, workspacePath: string, @@ -513,7 +511,7 @@ export function* inCheckout( } /** - * What one retained checkout's own Git configuration holds, key by key. + * What one saved checkout's own Git configuration holds, key by key. * * An absent key answers `undefined` rather than failing, which is the whole * point when a suite is proving that upstream tracking was never established: @@ -566,7 +564,7 @@ export function* checkoutConfig( } /** - * The commit one retained checkout is on, read out of the object itself. + * The commit one saved checkout is on, read out of the object itself. * * `cat-file commit` rather than a pretty format: what message verification * checks is the bytes in the object, and reading them through the same @@ -766,9 +764,9 @@ export function* removeWorkspacePath(database: WorkflowRunDatabase, path: string } /** - * Put one file into a retained checkout, with the mode a host would see. + * Put one file into a saved checkout, with the mode a host would see. * - * The retained-checkout surface a document reaches through ``, plus the + * The saved-checkout surface a document reaches through ``, plus the * one thing that component does not write: an executable bit. A hook is a * program only when it has one, so a suite that wants to prove a hook cannot run * has to be able to install one that could. @@ -792,12 +790,12 @@ export function* writeCheckoutFile( } /** - * The retained run value a record installs under, in its own version's shape. + * The saved run value a record installs under, in its own version's shape. * * A Git record installs the base and pinned commit it retains; a source-bundle * record installs its bundle hash and exact target and invents neither. */ -function retainedRunValue(record: WorkflowRunRecord): WorkflowRun { +function savedRunValue(record: WorkflowRunRecord): WorkflowRun { { return { runId: record.runId, diff --git a/packages/git/tests/support/git-crash-child.ts b/packages/git/tests/support/git-crash-child.ts index 0e362434c..98d51a709 100644 --- a/packages/git/tests/support/git-crash-child.ts +++ b/packages/git/tests/support/git-crash-child.ts @@ -1,7 +1,7 @@ /** * The two processes a Git crash proof needs, and neither of them is the test. * - * A `` mutates a retained checkout, publishes a Workspace root and + * A `` mutates a saved checkout, publishes a Workspace root and * appends its journal result inside one transaction. Whether those commit * together is decided by what happens when nothing gets to run: `SIGKILL` * performs no cleanup, no commit and no rollback, and the next connection to @@ -60,7 +60,7 @@ import { } from "../../../workflow/src/deno/workspace/private.ts"; import { createWorkspaceMetadata } from "../../src/deno/repositories.ts"; import { executeInstalled } from "@executablemd/core/host"; -import { retainedWorkflowInstallation } from "../../../workflow/src/run.ts"; +import { savedWorkflowInstallation } from "../../../workflow/src/run.ts"; import { denoRepositoryHost, useGitAuthentication } from "../../src/deno/composition/host.ts"; import { denoGitAuthentication } from "../../src/deno/composition/authentication.ts"; import type { GitInvocation, GitOutcome } from "../../src/deno/composition/host.ts"; @@ -246,7 +246,7 @@ function* pushCrash( yield* executeInstalled( { ...inlineSource(pushDocument(locator)), stream: database.journal }, [ - retainedWorkflowInstallation({ + savedWorkflowInstallation({ runId: record.runId, bundleHash: record.definition.bundleHash, }), diff --git a/packages/git/tests/support/issue-providers.ts b/packages/git/tests/support/issue-providers.ts index 333fddf8a..c52c103d3 100644 --- a/packages/git/tests/support/issue-providers.ts +++ b/packages/git/tests/support/issue-providers.ts @@ -209,7 +209,7 @@ export function useProviderComponents(log: ProviderLog): Operation { return yield* content(); } // A boundary that refuses everything, for content that must reach no - // provider at all. A replay standing on a retained result proves it by + // provider at all. A replay standing on a saved result proves it by // succeeding here: had it asked, this would have answered by failing. yield* IssueApi.around({ // deno-lint-ignore require-yield diff --git a/packages/git/tests/support/issue-scenario.ts b/packages/git/tests/support/issue-scenario.ts index 849bbe11d..42c39e07d 100644 --- a/packages/git/tests/support/issue-scenario.ts +++ b/packages/git/tests/support/issue-scenario.ts @@ -26,7 +26,7 @@ import { InMemoryStream } from "@executablemd/durable-streams"; import type { DurableEvent, DurableStream } from "@executablemd/durable-streams"; import { useTesting } from "@executablemd/testing"; import type { TestResult } from "@executablemd/testing"; -import { retainedWorkflowInstallation } from "../../../workflow/src/run.ts"; +import { savedWorkflowInstallation } from "../../../workflow/src/run.ts"; import type { WorkflowRun } from "../../../workflow/src/run.ts"; import { useCompositionComponents } from "../../src/composition/installation.ts"; import { ISSUE_EFFECT } from "../../src/issue/effect-type.ts"; @@ -40,7 +40,7 @@ import { } from "./issue-providers.ts"; import type { ProviderLog } from "./issue-providers.ts"; import { useGitHubIssues } from "../../src/deno/issue/github.ts"; -import { useRetainedIssueOperations } from "../../src/issue/effect.ts"; +import { useSavedIssueOperations } from "../../src/issue/effect.ts"; import { credential, useIssueTrackerServer } from "./issue-tracker-server.ts"; import type { IssueTrackerServer, ServedIssue } from "./issue-tracker-server.ts"; @@ -137,13 +137,13 @@ export interface ScenarioFixture { * No testing session: a staged attempt is a previous *run*, not a test. */ stage(document: string): Operation; - /** The staged attempts' retained journal, as the next attempt inherits it. */ + /** The staged attempts' saved journal, as the next attempt inherits it. */ journal(): DurableEvent[]; /** * Run a scenario document under its own complete testing session. * * Each asserting document executes against a journal of its own, named for - * the document. A retained history belongs to the document that wrote it, so + * the document. A saved history belongs to the document that wrote it, so * a second document replaying the first's would diverge on the first name * that did not match — and an asserting document is never a continuation of a * staged attempt anyway. What it asks about the scenario's journal it asks @@ -160,10 +160,10 @@ export function* useScenarioFixture(): Operation { const attempting: Attempting = { current: undefined }; yield* useCompositionComponents(); - // The retained lifecycle `` asks for, above whichever transport a + // The saved lifecycle `` asks for, above whichever transport a // scenario installs beneath it. The scenarios are about a workflow run's // durability, so it is the workflow one they run under. - yield* useRetainedIssueOperations(); + yield* useSavedIssueOperations(); yield* useProviderComponents(log); yield* useKeyRecorder(log); yield* useScenarioComponents(server, held, log, staged, attempting); @@ -203,7 +203,7 @@ function observeDocument( return scoped(function* () { const tests = yield* useTesting(); const execution = yield* executeInstalled({ path, stream: held.for(runId) }, [ - retainedWorkflowInstallation({ ...RUN, runId }), + savedWorkflowInstallation({ ...RUN, runId }), ]); // Drained to its close value first: output closes with the rendered text — // complete or partial — even when the execution settles Err, so a failing @@ -508,7 +508,7 @@ function useScenarioComponents( paths: server.requests.map((request) => request.path), // The scheme, never the credential. A document that received the // header itself would carry a credential in its own text and in the - // run's retained history — the one thing this contract says a history + // run's saved history — the one thing this contract says a history // never holds — and would not settle at all // (taras/executable.md#524). Whether the credential the tracker wants // actually arrived is decided here, where it is already known, and @@ -623,7 +623,7 @@ function useScenarioComponents( }, // deno-lint-ignore require-yield *fn(): Operation { - // Asked of every Issue effect the run retained rather than of the + // Asked of every Issue effect the run saved rather than of the // members somebody remembered to look at, and answered here rather than // handed over: the credential and the marker would not survive // rendering at all, and the rest would put a deployment's addresses and @@ -639,7 +639,7 @@ function useScenarioComponents( // where the run's own document wrote the ``, and this harness // loads that document by absolute path. It is removed before the scan // so the probe still catches a provider's or deployment's data leaking - // anywhere else in the retained effect. + // anywhere else in the saved effect. const events = held.snapshot(RUN.runId); const scanned = issueYields(events).map((event) => { if (event.type !== "yield") { @@ -847,7 +847,7 @@ interface AuthoredBody { * * The marker is reported as a boolean and never as text. It is a digest of the * idempotency key, so handing it to a document would put a run's internal - * identity into that document's rendered output and into its retained history, + * identity into that document's rendered output and into its saved history, * and a scenario proves the marker was written by saying that it was. */ function authoredIn(body: unknown): AuthoredBody { @@ -916,7 +916,7 @@ function stageAttempt( }); const execution = yield* executeInstalled({ path, stream }, [ - retainedWorkflowInstallation({ ...RUN, runId: id }), + savedWorkflowInstallation({ ...RUN, runId: id }), ]); const output = yield* drain(execution.output); const outcome = yield* execution; @@ -935,8 +935,8 @@ function stageAttempt( (event) => event.type === "yield" && event.description.type === ISSUE_EFFECT, ) : -1; - const retained = died === -1 ? continuing : continuing.slice(0, died); - held.truncate(id, retained); + const saved = died === -1 ? continuing : continuing.slice(0, died); + held.truncate(id, saved); return { ok: outcome.ok, output, @@ -944,7 +944,7 @@ function stageAttempt( // What the attempt *left behind*, not what it managed to write before it // stopped existing. An interrupted process journaled nothing, and the // count a scenario asks about is the one the next attempt inherits. - effects: issueYields(retained).length, + effects: issueYields(saved).length, calls: server.requests.length - before, }; }); diff --git a/packages/git/tests/support/pull-request-crash-child.ts b/packages/git/tests/support/pull-request-crash-child.ts index 5f075c983..68f8f5120 100644 --- a/packages/git/tests/support/pull-request-crash-child.ts +++ b/packages/git/tests/support/pull-request-crash-child.ts @@ -20,7 +20,7 @@ import { collect, inlineSource } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import { WorkflowRunStorage } from "@executablemd/workflow"; import { useWorkflowRunStorage } from "@executablemd/workflow/deno"; -import { retainedWorkflowInstallation } from "../../../workflow/src/run.ts"; +import { savedWorkflowInstallation } from "../../../workflow/src/run.ts"; import { gitWorkspaceAttachment } from "../../src/deno/attachment.ts"; import { gitPluginAdmissions } from "./composition.ts"; import { withWorkflowWorkspace } from "../../../workflow/src/deno/workspace/host.ts"; @@ -81,7 +81,7 @@ function* open(root: string, runId: string, locator: string, endpoint: string): stream: database.journal, }, [ - retainedWorkflowInstallation({ + savedWorkflowInstallation({ runId: record.runId, bundleHash: record.definition.bundleHash, }), diff --git a/packages/git/tests/support/replay.ts b/packages/git/tests/support/replay.ts index c69f8f890..55ef17fca 100644 --- a/packages/git/tests/support/replay.ts +++ b/packages/git/tests/support/replay.ts @@ -110,7 +110,7 @@ export function committedRoot(path: string): unknown { } /** - * Damage one retained row, and refuse to continue if it damaged none. + * Damage one saved row, and refuse to continue if it damaged none. * * A tamper that matched nothing leaves the run healthy, and a regression built * on one passes by testing the unmodified case. The row count is what makes the @@ -156,11 +156,11 @@ export function latestRoot(path: string): unknown { /** * Damage the checkout path in both places a run keeps it. * - * The retained row and the journaled record are separately authoritative, and + * The saved row and the journaled record are separately authoritative, and * the attachment check that compares them already refuses when only one has * moved. Damaging one would therefore test that comparison rather than what * happens when a path is *used* — so both are set to the same untrusted value, - * which is what a partial replay of damaged retained state actually looks like. + * which is what a partial replay of damaged saved state actually looks like. */ export function damageCheckoutPath(path: string, name: string, replacement: string): void { changedExactlyOne(path, "UPDATE workspace_repositories SET checkout_path = ? WHERE name = ?", [ @@ -203,7 +203,7 @@ export function isStale(candidate: unknown): candidate is RepositoryStaleStateEr * * The status is the outcome these assertions check. A refusal that the run * reports to the document and records as a success is a history that disagrees - * with itself, and only the retained result says which one happened. + * with itself, and only the saved result says which one happened. */ export function* compositionResults( database: WorkflowRunDatabase, @@ -226,7 +226,7 @@ export function* compositionResults( * substitution is aimed at. * * The staging half of every substitution suite: it produces a real tree on the - * host, and the caller imports it over the retained one through the test-only + * host, and the caller imports it over the saved one through the test-only * storage fixture. */ /** A host directory holding `tree`, ready to be imported over `workspacePath`. */ diff --git a/packages/git/tests/worktree-replay.test.ts b/packages/git/tests/worktree-replay.test.ts index ca4b1efd5..10e659445 100644 --- a/packages/git/tests/worktree-replay.test.ts +++ b/packages/git/tests/worktree-replay.test.ts @@ -43,8 +43,8 @@ import { raised, removeWorkspacePath, replaceWorkspaceTree, - retainedRepositories, - retainedWorktrees, + savedRepositories, + savedWorktrees, runDocument, subcommands, survivingRoots, @@ -70,14 +70,14 @@ import { /** * Substituting one repository's checkout for another's. * - * The bytes at a retained path are not the run's word by the time they are read + * The bytes at a saved path are not the run's word by the time they are read * back. A valid checkout of a different repository is a perfectly readable Git * repository, so attachment that asked only "can Git read this" accepted one - * identity's retained history against another identity's content: the document + * identity's saved history against another identity's content: the document * carried on restoring recorded reads that no longer described what was there, * and every later live effect ran against the substitute. * - * Each of these builds a real, valid checkout that differs from the retained one + * Each of these builds a real, valid checkout that differs from the saved one * in exactly one dimension of creation identity, so a passing test says which * check caught it rather than that something did. */ @@ -99,12 +99,12 @@ describe("workflow Worktree substituted checkouts", () => { ].join("\n"); yield* runDocument(database, document); - const [worktreeBefore] = yield* retainedWorktrees(database, "project"); + const [worktreeBefore] = yield* savedWorktrees(database, "project"); const checkout = worktreeBefore?.checkoutPath ?? ""; // A whole repository of its own, cloned from the same remote, standing - // where a linked worktree was retained. Its origin and object format both - // agree; what it is not is a worktree of the retained Repository. + // where a linked worktree was saved. Its origin and object format both + // agree; what it is not is a worktree of the saved Repository. const counting = countingHost(); const staged = yield* substitute(counting.host, checkout, function* (target, home) { git(["clone", "--no-hardlinks", "--", a.locator, target], home, home); @@ -122,7 +122,7 @@ describe("workflow Worktree substituted checkouts", () => { expect(String(rendered)).not.toContain("child marker"); expect(subcommands(counting.counters)).not.toContain("clone"); expect(subcommands(counting.counters)).not.toContain("worktree"); - expect(yield* retainedWorktrees(database, "project")).toEqual([worktreeBefore]); + expect(yield* savedWorktrees(database, "project")).toEqual([worktreeBefore]); }); }); }); diff --git a/packages/runtime/apis.ts b/packages/runtime/apis.ts index 693a1995f..90902051e 100644 --- a/packages/runtime/apis.ts +++ b/packages/runtime/apis.ts @@ -296,7 +296,7 @@ function* run(options: { }): Operation { return yield* scoped(function* () { // Before acquisition, so a chunk written while the child is being started - // is retained rather than raced for. + // is saved rather than raced for. const kept = options.retain ? yield* retaining() : undefined; const child = yield* processExec(options.command, { @@ -339,9 +339,9 @@ function* withTimeout( /** * What a finished child process reports. * - * `stdout` and `stderr` are the text a caller asked to be retained. A caller + * `stdout` and `stderr` are the text a caller asked to be saved. A caller * that asked for none gets `undefined` rather than an empty string: nothing was - * accumulated, and "no output" and "not retained" are different answers. + * accumulated, and "no output" and "not saved" are different answers. */ export interface ProcessOutcome { exitCode: number; diff --git a/packages/runtime/deno-agent-session-coordinator.ts b/packages/runtime/deno-agent-session-coordinator.ts index 13ea24d95..b704b5b2b 100644 --- a/packages/runtime/deno-agent-session-coordinator.ts +++ b/packages/runtime/deno-agent-session-coordinator.ts @@ -167,13 +167,13 @@ export function createDenoAgentSessionCoordinator( parsed = JSON.parse(text); } catch { throw new AgentSessionRecoveryRequired( - "the retained agent session ownership record is not readable", + "the saved agent session ownership record is not readable", ); } const record = parseAgentSessionOwnership(parsed); if (!record) { throw new AgentSessionRecoveryRequired( - "the retained agent session ownership record is not valid", + "the saved agent session ownership record is not valid", ); } return record; @@ -261,8 +261,8 @@ export function createDenoAgentSessionCoordinator( return Err(busy(key)); } - const retained = yield* readOwnership(record); - if (retained?.state === "active") { + const saved = yield* readOwnership(record); + if (saved?.state === "active") { // The lock is free and the record is not. Whoever held it last never // said it had stopped, and nothing here can say it for them. return Err( diff --git a/packages/runtime/executable-observer.ts b/packages/runtime/executable-observer.ts index a7cd9d454..0a2b66450 100644 --- a/packages/runtime/executable-observer.ts +++ b/packages/runtime/executable-observer.ts @@ -7,7 +7,7 @@ * provider accept the same session identity and disagree silently about what * it names — that is how issue #519's first gate produced a healthy-looking * session with no history in it. So before a session crosses an ownership - * boundary, the exact file about to run is observed, and enough is retained to + * boundary, the exact file about to run is observed, and enough is saved to * recognize it later. * * What is observed is a canonical path, the SHA-256 of that file's bytes, and @@ -17,7 +17,7 @@ * * This is a plain capability the trusted host builds and hands directly to the * provider that needs it. It is deliberately not a contextual Api. Executable - * validation decides which retained history is accepted, and a decision + * validation decides which saved history is accepted, and a decision * document middleware could replace is not one — a replaceable resolver could * point the observation at a different binary than the one that runs. * @@ -40,7 +40,7 @@ export type ExecutableRefusal = * An observation failure that names its reason. * * The reason is the actionable part and the message is diagnostic. Neither is - * retained: a caller turns this into its own refusal, and the paths involved + * saved: a caller turns this into its own refusal, and the paths involved * stay on this side of that boundary. */ export class ExecutableObservationError extends Error { diff --git a/packages/runtime/test/README.md b/packages/runtime/test/README.md index eb8eab8cc..91a1df4e4 100644 --- a/packages/runtime/test/README.md +++ b/packages/runtime/test/README.md @@ -211,7 +211,7 @@ is a real consumer that reads it. A substituted provider that ignores it makes an authored `fetch(url).expect()` succeed on a 404. Count what the provider was asked to perform separately from what a document -bound or a journal retained: those three can agree while the number of requests +bound or a journal saved: those three can agree while the number of requests is wrong. Middleware that answers without delegating performs no request at all, which is substitution rather than reach. diff --git a/packages/runtime/test/stubs.ts b/packages/runtime/test/stubs.ts index 74130228c..7a7217d6b 100644 --- a/packages/runtime/test/stubs.ts +++ b/packages/runtime/test/stubs.ts @@ -97,7 +97,7 @@ export function* useStubFs(files: Record): Operation { * * It answers like a real child: the text is written to the stdio chain as it * "arrives", so whatever encloses the call sees it the way it would see a - * child's, and it is retained in the outcome only when the caller asked + * child's, and it is saved in the outcome only when the caller asked * for retention. A stub that always returned strings would let a document * render output the contract says was already displayed. */ @@ -107,7 +107,7 @@ export function* useEchoExec(): Operation { const script = (options.command[2] ?? "").trim(); const text = script.startsWith("echo ") ? script.slice(5) + "\n" : script + "\n"; yield* Stdio.operations.stdout(encoder.encode(text)); - return retained(options, { exitCode: 0, stdout: text, stderr: "" }); + return saved(options, { exitCode: 0, stdout: text, stderr: "" }); }, }); } @@ -121,7 +121,7 @@ export function* useFailingExec(exitCode: number, stderr = "command failed"): Op yield* API.Process.around({ *exec([options], _next) { yield* Stdio.operations.stderr(encoder.encode(stderr)); - return retained(options, { exitCode, stdout: "", stderr }); + return saved(options, { exitCode, stdout: "", stderr }); }, }); } @@ -129,7 +129,7 @@ export function* useFailingExec(exitCode: number, stderr = "command failed"): Op const encoder = new TextEncoder(); /** What the caller asked to keep, so a stub cannot retain more than a call would. */ -function retained( +function saved( options: { retain?: boolean }, outcome: { exitCode: number; stdout: string; stderr: string }, ): { exitCode: number; stdout: string | undefined; stderr: string | undefined } { diff --git a/packages/runtime/tests/agent-session-coordinator.test.ts b/packages/runtime/tests/agent-session-coordinator.test.ts index 670aac904..95a8cd143 100644 --- a/packages/runtime/tests/agent-session-coordinator.test.ts +++ b/packages/runtime/tests/agent-session-coordinator.test.ts @@ -40,8 +40,8 @@ function owner(kind: "session" | "prompt" | "native-launch", id: string) { return { kind, operationId: id }; } -/** The retained ownership record, read as another process would. */ -function* retained(root: string, key: AgentSessionKey): Operation { +/** The saved ownership record, read as another process would. */ +function* saved(root: string, key: AgentSessionKey): Operation { const file = join(root, "ownership", `${agentSessionKeyDigest(key)}.json`); const text = yield* until(readFile(file, "utf8").catch(() => undefined)); return text === undefined ? undefined : parseAgentSessionOwnership(JSON.parse(text)); @@ -89,14 +89,14 @@ describe("Tier CO — the agent session coordinator", () => { owner("native-launch", "op-1"), function* (ownership) { // Active while the body runs: that is what a later owner refuses on. - expect(yield* retained(root, KEY)).toMatchObject({ state: "active" }); + expect(yield* saved(root, KEY)).toMatchObject({ state: "active" }); ownership.quiesced(); return "done"; }, ); expect(outcome.ok && outcome.value).toBe("done"); - expect(yield* retained(root, KEY)).toMatchObject({ + expect(yield* saved(root, KEY)).toMatchObject({ state: "idle", ownerKind: "native-launch", operationId: "op-1", @@ -163,7 +163,7 @@ describe("Tier CO — the agent session coordinator", () => { // right — nothing here can say what that owner had already done. const root = yield* useTempDirectory("xmd-co-"); const child = yield* holder(root, KEY); - expect(yield* retained(root, KEY)).toMatchObject({ state: "active" }); + expect(yield* saved(root, KEY)).toMatchObject({ state: "active" }); child.kill(); @@ -196,7 +196,7 @@ describe("Tier CO — the agent session coordinator", () => { expect(outcome.ok).toBe(false); expect(outcome.ok === false && outcome.error.name).toBe("AgentSessionRecoveryRequired"); // Still owned, and still by the owner that died. - expect(yield* retained(root, KEY)).toMatchObject({ + expect(yield* saved(root, KEY)).toMatchObject({ state: "active", operationId: "00000000-0000-4000-8000-000000000001", }); diff --git a/packages/runtime/tests/executable-observer.test.ts b/packages/runtime/tests/executable-observer.test.ts index b48d85354..b5500daea 100644 --- a/packages/runtime/tests/executable-observer.test.ts +++ b/packages/runtime/tests/executable-observer.test.ts @@ -177,12 +177,12 @@ describe("Tier EO — executable observation", () => { // And the durable half of an observation is exactly two facts. const file = yield* script(secret, "claude", "2.1.235"); const observed = yield* observer.observe(file); - const retained = { + const saved = { schema: "executable-build.v1", reportedVersion: observed.versionOutput.trim(), executableDigest: observed.digest, }; - expect(JSON.stringify(retained)).not.toContain(root); + expect(JSON.stringify(saved)).not.toContain(root); expect(Object.keys(observed).sort()).toEqual(["digest", "path", "versionOutput"]); }); }); diff --git a/packages/runtime/tests/process.test.ts b/packages/runtime/tests/process.test.ts index 19ab87306..c6801dc72 100644 --- a/packages/runtime/tests/process.test.ts +++ b/packages/runtime/tests/process.test.ts @@ -80,7 +80,7 @@ describe("Tier FG — process retention", () => { retain: false, }); - // "no output" and "not retained" are different answers, and this is the + // "no output" and "not saved" are different answers, and this is the // second one. expect(result.stdout).toBeUndefined(); expect(result.stderr).toBeUndefined(); diff --git a/packages/test-agent/src/session-coordinator.ts b/packages/test-agent/src/session-coordinator.ts index 9e3194f62..9906caeff 100644 --- a/packages/test-agent/src/session-coordinator.ts +++ b/packages/test-agent/src/session-coordinator.ts @@ -3,7 +3,7 @@ * partition (specs/test-agent-spec.md §Scenarios). * * The worker belongs to this process, so coordination is deterministic in-memory - * state: one live owner at a time, with ownership retained until quiescence is + * state: one live owner at a time, with ownership saved until quiescence is * proven. There is no machine-wide namespace. Sibling tests naming the same agent, * session and directory own separate partitions and do not contend. */ @@ -32,7 +32,7 @@ export function createDeterministicSessionCoordinator(): AgentSessionCoordinator /** Held right now, by digest. */ const occupied = new Set(); /** What the last owner left behind. Absent is the same as idle. */ - const retained = new Map(); + const saved = new Map(); return { coordinate( @@ -57,7 +57,7 @@ export function createDeterministicSessionCoordinator(): AgentSessionCoordinator occupied.delete(digest); }); - if (retained.get(digest) === "active") { + if (saved.get(digest) === "active") { return Err( new AgentSessionRecoveryRequired( `session "${key.sessionKey}" was left owned by work that did not finish, so it ` + @@ -65,14 +65,14 @@ export function createDeterministicSessionCoordinator(): AgentSessionCoordinator ), ); } - retained.set(digest, "active"); + saved.set(digest, "active"); let quiesced = false; // Registered before the body runs, so the idle mark is published on // every ordinary exit — and on none of the others. yield* ensure(() => { if (quiesced) { - retained.set(digest, "idle"); + saved.set(digest, "idle"); } }); diff --git a/packages/test-agent/tests/controller.test.ts b/packages/test-agent/tests/controller.test.ts index f2acf68ec..c90cdc38e 100644 --- a/packages/test-agent/tests/controller.test.ts +++ b/packages/test-agent/tests/controller.test.ts @@ -353,7 +353,7 @@ describe("Tier TC — controller", () => { yield* task.halt(); // The instance finalizer revoked the worker before the server closed; - // the retained instance shows its state cleared. + // the saved instance shows its state cleared. yield* client.closed; expect(info.scenario.journal.length).toBe(0); expect(info.scenario.failure).toBe(undefined); diff --git a/packages/test-agent/tests/native-launch.test.ts b/packages/test-agent/tests/native-launch.test.ts index 3f274d040..e89bcda97 100644 --- a/packages/test-agent/tests/native-launch.test.ts +++ b/packages/test-agent/tests/native-launch.test.ts @@ -194,7 +194,7 @@ describe( const nativeSessionId = run.sessions.at(-1)!.nativeSessionId; expect(nativeSessionId.startsWith("native-")).toBe(true); expect(run.launches[0]?.command).toEqual(["xmd-test-agent-ui", "--resume", nativeSessionId]); - // And the retained record names that same identity. + // And the saved record names that same identity. expect(preparedRecords(run.events)[0]?.nativeSessionId).toBe(nativeSessionId); }); @@ -242,7 +242,7 @@ describe( expect(run.result.ok).toBe(true); // Reattaching re-establishes the session rather than reusing the handle // that predates the handoff, and it lands on the provider-native state the - // launch retained. + // launch saved. const launched = preparedRecords(run.events)[0]?.nativeSessionId; expect(launched).toBeDefined(); expect(run.sessions.at(-1)?.nativeSessionId).toBe(launched); diff --git a/packages/test-support/journal.ts b/packages/test-support/journal.ts index f1930c4d1..faf7560a2 100644 --- a/packages/test-support/journal.ts +++ b/packages/test-support/journal.ts @@ -3,7 +3,7 @@ * * These are pure projections over an array. They open no database, own no * effect vocabulary beyond the four replay-bookkeeping types below, and parse - * a retained record only through the parser the package that defines that + * a saved record only through the parser the package that defines that * record supplies. A suite that needs a different selection asks for it by * effect type rather than redefining what this repository calls bookkeeping. */ @@ -17,11 +17,11 @@ export interface SelectedYield { readonly event: Yield; } -/** What a package that owns a record type does with a retained value. */ -export type RetainedRecordParser = (value: unknown) => T | undefined; +/** What a package that owns a record type does with a saved value. */ +export type SavedRecordParser = (value: unknown) => T | undefined; -/** What one selected effect settled as, and what it retained when it settled. */ -export type RetainedOutcome = +/** What one selected effect settled as, and what it saved when it settled. */ +export type SavedOutcome = | { readonly position: number; readonly effect: string; @@ -91,37 +91,37 @@ export function effectSequence(events: readonly DurableEvent[]): readonly string /** * What each Yield of this effect type settled as, parsing `result.value`. * - * The layout a Git-host reconciliation record is retained in: the record is + * The layout a Git-host reconciliation record is saved in: the record is * the result value itself. */ -export function readRetainedValues( +export function readSavedValues( events: readonly DurableEvent[], effect: string, - parse: RetainedRecordParser, -): readonly RetainedOutcome[] { + parse: SavedRecordParser, +): readonly SavedOutcome[] { return read(events, effect, parse, (value) => value); } /** * What each Yield of this effect type settled as, parsing `result.value.record`. * - * The layout a Workspace composition or Git envelope is retained in: the + * The layout a Workspace composition or Git envelope is saved in: the * record is a member of the result value. */ -export function readRetainedRecords( +export function readSavedRecords( events: readonly DurableEvent[], effect: string, - parse: RetainedRecordParser, -): readonly RetainedOutcome[] { + parse: SavedRecordParser, +): readonly SavedOutcome[] { return read(events, effect, parse, nestedRecord); } function read( events: readonly DurableEvent[], effect: string, - parse: RetainedRecordParser, - retained: (value: Json) => unknown, -): readonly RetainedOutcome[] { + parse: SavedRecordParser, + saved: (value: Json) => unknown, +): readonly SavedOutcome[] { return selectYields(events, [effect]).map((selected) => { const { position } = selected; const result = selected.event.result; @@ -131,12 +131,10 @@ function read( if (result.status === "err") { return { position, effect, status: result.status, error: result.error }; } - const value = result.value === undefined ? undefined : retained(result.value); + const value = result.value === undefined ? undefined : saved(result.value); const record = value === undefined ? undefined : parse(value); if (record === undefined) { - throw new Error( - `the value retained at ${position} ${effect} is not a record its parser reads`, - ); + throw new Error(`the value saved at ${position} ${effect} is not a record its parser reads`); } return { position, effect, status: result.status, record }; }); diff --git a/packages/test-support/tests/journal.test.ts b/packages/test-support/tests/journal.test.ts index 024859997..f8342fa37 100644 --- a/packages/test-support/tests/journal.test.ts +++ b/packages/test-support/tests/journal.test.ts @@ -3,7 +3,7 @@ * * Hand-written events and parser spies, because what is under test is the * projection itself: no database is opened here, and no effect-specific parser - * is imported. The retained payloads carry a sentinel standing in for a + * is imported. The saved payloads carry a sentinel standing in for a * credential, so a diagnostic that echoed one would be visible. */ @@ -12,14 +12,14 @@ import { expect } from "@executablemd/test-support/expect"; import { readTextFile } from "@effectionx/fs"; import { effectSequence, - readRetainedRecords, - readRetainedValues, + readSavedRecords, + readSavedValues, selectYields, } from "@executablemd/test-support/journal"; -import type { RetainedRecordParser } from "@executablemd/test-support/journal"; +import type { SavedRecordParser } from "@executablemd/test-support/journal"; import type { Close, DurableEvent, Json, Yield } from "@executablemd/durable-streams"; -const SENTINEL = "retained-credential-sentinel"; +const SENTINEL = "saved-credential-sentinel"; function settled(type: string, value: Json): Yield { return { @@ -68,7 +68,7 @@ interface Reconciliation { /** A parser that owns its record type, and counts what it was asked to read. */ function reconciliations(): { - parse: RetainedRecordParser; + parse: SavedRecordParser; seen: unknown[]; } { const seen: unknown[] = []; @@ -147,7 +147,7 @@ describe("test-support journal reader", () => { settled("git_host_effect", { kind: "pull-request", decision: "adopted" }), ]; - const outcomes = readRetainedValues(events, "git_host_effect", parse); + const outcomes = readSavedValues(events, "git_host_effect", parse); expect(outcomes).toEqual([ { @@ -182,7 +182,7 @@ describe("test-support journal reader", () => { }), ]; - expect(readRetainedRecords(events, "workspace_repository", parse)).toEqual([ + expect(readSavedRecords(events, "workspace_repository", parse)).toEqual([ { position: 0, effect: "workspace_repository", @@ -202,7 +202,7 @@ describe("test-support journal reader", () => { cancelled("git_host_effect"), ]; - expect(readRetainedValues(events, "git_host_effect", parse)).toEqual([ + expect(readSavedValues(events, "git_host_effect", parse)).toEqual([ { position: 0, effect: "git_host_effect", @@ -229,11 +229,9 @@ describe("test-support journal reader", () => { unsettled("git_host_effect"), ]; - const message = refusal(() => readRetainedValues(events, "git_host_effect", parse)); + const message = refusal(() => readSavedValues(events, "git_host_effect", parse)); - expect(message).toBe( - "the value retained at 1 git_host_effect is not a record its parser reads", - ); + expect(message).toBe("the value saved at 1 git_host_effect is not a record its parser reads"); expect(message).not.toContain(SENTINEL); }); @@ -243,10 +241,10 @@ describe("test-support journal reader", () => { const { parse } = reconciliations(); const events: DurableEvent[] = [settled("workspace_repository", { token: SENTINEL })]; - const message = refusal(() => readRetainedRecords(events, "workspace_repository", parse)); + const message = refusal(() => readSavedRecords(events, "workspace_repository", parse)); expect(message).toBe( - "the value retained at 0 workspace_repository is not a record its parser reads", + "the value saved at 0 workspace_repository is not a record its parser reads", ); expect(message).not.toContain(SENTINEL); }); @@ -259,11 +257,9 @@ describe("test-support journal reader", () => { settled("git_host_effect", { decision: "performed", token: SENTINEL }), ]; - const message = refusal(() => readRetainedValues(events, "git_host_effect", parse)); + const message = refusal(() => readSavedValues(events, "git_host_effect", parse)); - expect(message).toBe( - "the value retained at 0 git_host_effect is not a record its parser reads", - ); + expect(message).toBe("the value saved at 0 git_host_effect is not a record its parser reads"); expect(message).not.toContain(SENTINEL); }); diff --git a/packages/testing/src/activation.ts b/packages/testing/src/activation.ts index 5e037eddf..628f57d1b 100644 --- a/packages/testing/src/activation.ts +++ b/packages/testing/src/activation.ts @@ -42,7 +42,7 @@ * acceptance is the default handler of an Api instance created for that one * call and reachable from nowhere else. A handler that answers without * delegating, substitutes either argument, reconstructs the Api name, delegates - * twice, or replays a retained request or a retired credential can refuse the + * twice, or replays a saved request or a retired credential can refuse the * call — none of them can make that terminal record it as accepted. */ diff --git a/packages/testing/src/execution-harness.ts b/packages/testing/src/execution-harness.ts index 3d6ea4741..ed3c73c5e 100644 --- a/packages/testing/src/execution-harness.ts +++ b/packages/testing/src/execution-harness.ts @@ -308,7 +308,7 @@ function* CollectJournal(): Operation { state.configuration.collectJournal = true; return ""; } - // Read-only, and only what the host already retained: observation never + // Read-only, and only what the host already saved: observation never // enabled retention, so there is nothing here to append to or replay from. return Object.freeze([...(state.settlement?.journal ?? [])]); } @@ -498,7 +498,7 @@ function readProfile( * `` — one real workflow run, in storage the test owns. * * The isolation is temporary ownership rather than simulated durability: the - * start, the journal, the suspension, the retained result and the resume are + * start, the journal, the suspension, the saved result and the resume are * the production ones, kept somewhere that belongs to this scope and is removed * with it. Two scopes declaring the same public id are still two runs, because * the storage is per-scope and the id is only what the run calls itself. diff --git a/packages/testing/src/execution-host.ts b/packages/testing/src/execution-host.ts index 087f803c9..535e5fdda 100644 --- a/packages/testing/src/execution-host.ts +++ b/packages/testing/src/execution-host.ts @@ -273,7 +273,7 @@ export interface ChildSettlement { readonly outcome: ExecutionOutcome; /** Complete output, or the partial prefix rendered before a failure. */ readonly output: string; - /** The child journal's completed snapshot, when the profile retained one. */ + /** The child journal's completed snapshot, when the profile saved one. */ readonly journal?: readonly DurableEvent[]; } diff --git a/packages/testing/tests/test-component.test.ts b/packages/testing/tests/test-component.test.ts index d4ffa61a7..18c3b3590 100644 --- a/packages/testing/tests/test-component.test.ts +++ b/packages/testing/tests/test-component.test.ts @@ -20,7 +20,7 @@ import { runDoc } from "./helpers.ts"; describe(" as a function component", () => { beforeAll(() => useTempFileCompiler()); - /** An eval block whose retained work fails while the test is dismantled. */ + /** An eval block whose saved work fails while the test is dismantled. */ const FAILING_CLEANUP = [ "```js persist eval", 'yield* spawn(function* () { try { yield* suspend(); } finally { throw new Error("cleanup exploded"); } });', diff --git a/packages/web/src/opener.ts b/packages/web/src/opener.ts index 892270560..151190e5c 100644 --- a/packages/web/src/opener.ts +++ b/packages/web/src/opener.ts @@ -36,7 +36,7 @@ export const FormOpener: Api = createApi("FormOpen *open(url: string): Operation { const { os } = yield* platform(); const command = openCommand(os, url); - // Retained on purpose: a browser command that fails explains itself in the + // Saved on purpose: a browser command that fails explains itself in the // error this throws, and there is no reader watching it live. const result = yield* exec({ command, retain: true }); const stderr = result.stderr?.trim() ?? ""; diff --git a/packages/workflow/deno.ts b/packages/workflow/deno.ts index 1132e0de3..f0387d598 100644 --- a/packages/workflow/deno.ts +++ b/packages/workflow/deno.ts @@ -39,7 +39,7 @@ export type { } from "./src/lifecycle/execution.ts"; export type { WorkflowLifecycleOptions } from "./src/deno/lifecycle.ts"; /** - * The closure a retained definition returns. + * The closure a saved definition returns. * * Published here rather than from the package root because these describe what * this adapter retains: a closure is checked against DOFS and SQLite rows. The @@ -47,7 +47,7 @@ export type { WorkflowLifecycleOptions } from "./src/deno/lifecycle.ts"; * from any entrypoint. */ export { gitBlobIdentity } from "./src/deno/artifact/source.ts"; -export type { RetainedDefinitionSources, RetainedSource } from "./src/lifecycle/source.ts"; +export type { SavedDefinitionSources, SavedSource } from "./src/lifecycle/source.ts"; export type { DetachedXmdArtifact, VerifiedXmdArtifact } from "./src/deno/artifact/types.ts"; export { hashRunId, @@ -92,7 +92,7 @@ export type { * * The other half of the same boundary, for work that exports a checkout or * proves a record still describes what is there. It journals nothing, publishes - * nothing, and can write neither bytes nor rows; a retained root is + * nothing, and can write neither bytes nor rows; a saved root is * materialized inside a rollback-only savepoint this package owns and is always * taken back. */ @@ -172,7 +172,7 @@ export { transactAgentSessions } from "./src/deno/workspace/private.ts"; export type { AgentSessions } from "./src/deno/workspace/agent-sessions.ts"; export { createWorkflowPromptPublisher } from "./src/deno/agent-publication.ts"; export type { - RetainedSessionKey, + SavedSessionKey, WorkflowPromptPublisherOptions, } from "./src/deno/agent-publication.ts"; export { transactAgentPromptCheckpoints } from "./src/deno/workspace/private.ts"; diff --git a/packages/workflow/mod.ts b/packages/workflow/mod.ts index 0c2565d2c..65e89ef85 100644 --- a/packages/workflow/mod.ts +++ b/packages/workflow/mod.ts @@ -5,29 +5,29 @@ * * A workflow run is a run of one immutable definition, recorded durably before * the root document is imported so later document executions and durable - * effects share one explicit identity. The run itself is retained, so another + * effects share one explicit identity. The run itself is saved, so another * process can find it by its public id and continue from durable data rather * than from whoever happened to be holding the journal. * * A definition is the exact bytes themselves, addressed by portable logical - * paths and retained with the run. That is what lets a file outside Git, an + * paths and saved with the run. That is what lets a file outside Git, an * untracked file, and a file edited since its last commit each be one - * immutable definition the moment it is retained — and it is why nothing here + * immutable definition the moment it is saved — and it is why nothing here * reaches a repository for source. Starting, resuming, forking and exporting * all read the run's own store, so there is no source capability for a host to * supply and no definition whose bytes live anywhere but in the run. * - * `retainedWorkflowInstallation()` resolves no base and names no Git feature, + * `savedWorkflowInstallation()` resolves no base and names no Git feature, * and that is now a statement about what a run needs rather than only about * this package's imports. * * ```ts - * import { retainedWorkflowInstallation } from "@executablemd/workflow"; + * import { savedWorkflowInstallation } from "@executablemd/workflow"; * import { executeInstalled } from "@executablemd/core/host"; * * const execution = yield* executeInstalled( * { path: "./workflow.md", stream }, - * [retainedWorkflowInstallation(run)], + * [savedWorkflowInstallation(run)], * ); * ``` * @@ -41,12 +41,12 @@ * Repository composition, the Git capability, issue and pull-request contracts * and the Git-host reconciliation engine belong to `@executablemd/git`, which * imports this package's public extension boundaries rather than the other way - * round. A run's history still holds their retained records, and this package + * round. A run's history still holds their saved records, and this package * still reads enough of one to decide whether a checkpoint can be forked — as * compatibility data, named by the strings a released build wrote. */ -export { getWorkflowRun, retainedWorkflowInstallation } from "./src/run.ts"; +export { getWorkflowRun, savedWorkflowInstallation } from "./src/run.ts"; /** * How a trusted host states what its own run is. * @@ -60,12 +60,12 @@ export type { WorkflowRunPreparation } from "./src/run.ts"; export { workflowBundleInstallation, WorkflowBundleHistoryError } from "./src/bundle.ts"; export { workflowRunValue } from "./src/journal.ts"; /** - * The retained run value, and the refusal a run that is not it travels in. + * The saved run value, and the refusal a run that is not it travels in. * - * The description names the exact retained identity a run writes; the refusal + * The description names the exact saved identity a run writes; the refusal * is the exact words a disagreement with it travels in. */ -export { describeWorkflowRun, retainedRunMismatch } from "./src/journal.ts"; +export { describeWorkflowRun, savedRunMismatch } from "./src/journal.ts"; export type { WorkflowRun } from "./src/journal.ts"; export { useWorkflowServiceDenial, WorkflowServiceDeniedError } from "./src/service-denial.ts"; @@ -83,7 +83,7 @@ export type { } from "./src/storage/api.ts"; export type { WorkflowRunCreation } from "./src/lifecycle/execution.ts"; -export type { RetainedDefinitionSources, RetainedSource } from "./src/lifecycle/source.ts"; +export type { SavedDefinitionSources, SavedSource } from "./src/lifecycle/source.ts"; export { WorkflowLifecycle, WorkflowLifecycleProviderError } from "./src/lifecycle/api.ts"; export type { @@ -95,7 +95,7 @@ export type { WorkflowLifecycleApi, WorkflowLifecycleSnapshot, } from "./src/lifecycle/api.ts"; -// The export request, its result and the boundary it names. The retained record +// The export request, its result and the boundary it names. The saved record // shapes an artifact also carries are DOFS and SQLite rows, so they are the // Deno entrypoint's to publish rather than this one's. export type { diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 31f190e26..bec536a10 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,7 +1,7 @@ { "name": "@executablemd/workflow", "version": "0.13.1", - "description": "Workflow runs for executable.md: one pinned definition per run, retained durably so another process can find it.", + "description": "Workflow runs for executable.md: one pinned definition per run, saved durably so another process can find it.", "type": "module", "exports": { ".": "./mod.ts", diff --git a/packages/workflow/src/bundle.ts b/packages/workflow/src/bundle.ts index 42e41f5a8..213e4444a 100644 --- a/packages/workflow/src/bundle.ts +++ b/packages/workflow/src/bundle.ts @@ -11,10 +11,10 @@ * the exact sources. Canonical core captures it before any installation, * middleware, or document code exists, resolves declared names against it, * and invokes only the definitions it produced from it. - * - **Retained history.** `admissions` holds every recorded component import + * - **Saved history.** `admissions` holds every recorded component import * to the same catalog, inside canonical core's own journal read: before - * public replay policy, before any retained effect reaches execution, before - * a retained terminal result is reused, and before anything is appended. A + * public replay policy, before any saved effect reaches execution, before + * a saved terminal result is reused, and before anything is appended. A * history that recorded a name this bundle does not declare, a path or hash * the definition does not name, or source the commit does not hold, appends * nothing and invokes nothing. @@ -41,7 +41,7 @@ const IMPORT_COMPONENT = "import_component"; const WORKFLOW_SELECTION_MEMBERS: readonly string[] = ["kind", "path", "sourceHash", "content"]; /** - * A retained component import this run's bundle does not authorize. + * A saved component import this run's bundle does not authorize. * * Fixed diagnostics throughout. What a history recorded is journal data, and a * refusal that quoted a planted name, path, or source would publish exactly the @@ -52,14 +52,14 @@ export class WorkflowBundleHistoryError extends Error { } const REFUSALS = { - unreadable: "A retained component import cannot be read by this run's component bundle.", - undeclared: "A retained component import names a component this run's bundle does not declare.", - mismatched: "A retained component import does not match the component this run's bundle holds.", + unreadable: "A saved component import cannot be read by this run's component bundle.", + undeclared: "A saved component import names a component this run's bundle does not declare.", + mismatched: "A saved component import does not match the component this run's bundle holds.", wrongKind: - "A retained component import recorded a declared component as something other than a " + + "A saved component import recorded a declared component as something other than a " + "component this run's bundle supplies.", repository: - "A retained component import recorded a repository file, which a workflow run resolves none of.", + "A saved component import recorded a repository file, which a workflow run resolves none of.", } as const; /** @@ -77,7 +77,7 @@ function reading(read: () => T): T | undefined { } } -/** The name a retained event imported, when the event is a settled import at all. */ +/** The name a saved event imported, when the event is a settled import at all. */ function importedName(event: DurableEvent): string | undefined { const type = reading(() => event.type); if (type !== "yield") { @@ -93,7 +93,7 @@ function importedName(event: DurableEvent): string | undefined { } /** - * A retained value that refuses to be read. + * A saved value that refuses to be read. * * Distinct from absence. A settlement that is simply not there is an import * that failed, which this admission has nothing to say about; a settlement that @@ -126,7 +126,7 @@ function importedValue(event: DurableEvent): { value: unknown } | undefined | ty } /** - * Hold one retained import to the bundle, or refuse it. + * Hold one saved import to the bundle, or refuse it. * * Every branch is a decision about what the record *is*, taken before anything * is replayed from it. A declared name must have been recorded as a bundled @@ -194,8 +194,8 @@ function admitImport( function admits(components: ReadonlyMap): JournalAdmission { // deno-lint-ignore require-yield - return function* (retained: readonly DurableEvent[]) { - for (const event of retained) { + return function* (saved: readonly DurableEvent[]) { + for (const event of saved) { admitImport(event, components); } }; @@ -211,7 +211,7 @@ function admits(components: ReadonlyMap): Journ * * ```ts * yield* executeInstalled(options, [ - * retainedWorkflowInstallation(run), + * savedWorkflowInstallation(run), * workflowBundleInstallation(components), * ]); * ``` diff --git a/packages/workflow/src/deno/agent-publication.ts b/packages/workflow/src/deno/agent-publication.ts index 4e5ab6731..0df897f24 100644 --- a/packages/workflow/src/deno/agent-publication.ts +++ b/packages/workflow/src/deno/agent-publication.ts @@ -14,12 +14,12 @@ * its association was lost would silently become one this run cannot continue * from. A failure anywhere before the commit leaves neither. * - * ## What is retained, and what is not + * ## What is saved, and what is not * * Only a completion the provider named, in a session this run retains. Both are * supplied — the token by the provider through core's delivered coordinator, the * logical session key by the placement that made the session — and neither is - * derived. In particular the retained key is never recovered from the spelling + * derived. In particular the saved key is never recovered from the spelling * of the provider's own session key: those are two different namespaces that * happen to share a prefix, and reading one out of the other is a parse that * would quietly keep working while it was wrong. @@ -43,15 +43,15 @@ import { * How this host names the session one completion belonged to. * * Answers `undefined` for a session it did not place. Absent is ordinary: the - * Prompt publishes, and nothing is retained beside it. + * Prompt publishes, and nothing is saved beside it. */ -export type RetainedSessionKey = (providerSessionKey: string) => string | undefined; +export type SavedSessionKey = (providerSessionKey: string) => string | undefined; export interface WorkflowPromptPublisherOptions { /** The exact run this publisher publishes to. */ readonly database: WorkflowRunDatabase; /** What this run retains each placed session under. */ - readonly retainedSessionKey: RetainedSessionKey; + readonly savedSessionKey: SavedSessionKey; } /** A publication that did not produce exactly the one event it was for. */ @@ -69,7 +69,7 @@ class WorkflowPromptPublicationError extends WorkflowTransactionError { export function createWorkflowPromptPublisher( options: WorkflowPromptPublisherOptions, ): AgentPromptPublisher { - const { database, retainedSessionKey } = options; + const { database, savedSessionKey } = options; function* retain( workspace: PrivateWorkspaceTransaction, @@ -85,7 +85,7 @@ export function createWorkflowPromptPublisher( // attached to whichever of them happened to come first. throw new WorkflowPromptPublicationError( `publishing one Prompt appended ${appended.length} journal events, so this run cannot ` + - "say which event it just retained.", + "say which event it just saved.", ); } @@ -93,10 +93,10 @@ export function createWorkflowPromptPublisher( if (association === undefined) { return; } - const sessionKey = retainedSessionKey(association.sessionKey); + const sessionKey = savedSessionKey(association.sessionKey); if (sessionKey === undefined) { // A session this attachment did not place. The Prompt is published; - // there is simply no retained session to hang a checkpoint from. + // there is simply no saved session to hang a checkpoint from. return; } workspace.agentCheckpoints.associate({ diff --git a/packages/workflow/src/deno/answers.ts b/packages/workflow/src/deno/answers.ts index d7c854588..bc7e98d99 100644 --- a/packages/workflow/src/deno/answers.ts +++ b/packages/workflow/src/deno/answers.ts @@ -1,7 +1,7 @@ /** * The rows one run keeps for the answers delivered to its durable waits. * - * A retained answer is a row rather than a journal event, because delivery and + * A saved answer is a row rather than a journal event, because delivery and * execution are different operations. The value arrives while nothing is * running; it becomes history only when an execution reaches the wait it * answers and publishes it. Until then the row is what the run holds and the @@ -10,7 +10,7 @@ * Each row carries the identity of the wait it answers three ways over: the * suspension id it was delivered against, the exact journal event the request * was published as, and a fingerprint of the request and its response schema. - * The fingerprint is what a resume compares — a run whose retained request at + * The fingerprint is what a resume compares — a run whose saved request at * that position is not the one the value was judged against is a run this * answer was never for. */ @@ -31,16 +31,16 @@ const CONSUME_ANSWER = `UPDATE workflow_suspension_answers SET state = 'consumed', consumed_at = ? WHERE suspension_id = ? AND state = 'pending'`; -/** Whether a retained answer is still waiting to be published, or already was. */ -export type RetainedAnswerState = "pending" | "consumed"; +/** Whether a saved answer is still waiting to be published, or already was. */ +export type SavedAnswerState = "pending" | "consumed"; /** One delivered answer, as this run retains it. */ -export interface RetainedAnswer { +export interface SavedAnswer { readonly suspensionId: string; readonly requestEventId: string; readonly requestFingerprint: string; readonly answer: Json; - readonly state: RetainedAnswerState; + readonly state: SavedAnswerState; readonly createdAt: string; readonly consumedAt: string | undefined; } @@ -54,24 +54,24 @@ export interface PendingAnswerInsertion { readonly createdAt: string; } -/** The answer retained for this wait, whatever state it is in. */ -export function readRetainedAnswer( +/** The answer saved for this wait, whatever state it is in. */ +export function readSavedAnswer( database: DatabaseSync, suspensionId: string, -): RetainedAnswer | undefined { +): SavedAnswer | undefined { const row = reading(database, SELECT_ANSWER).get(suspensionId); - return row === undefined ? undefined : parseRetainedAnswer(row); + return row === undefined ? undefined : parseSavedAnswer(row); } /** * Every answer this run retains, in one deterministic order. * * What an export seals. Ordered by the wait each one answers rather than by - * insertion, so the same retained state reads the same however SQLite would + * insertion, so the same saved state reads the same however SQLite would * have returned the rows. */ -export function readAllRetainedAnswers(database: DatabaseSync): RetainedAnswer[] { - return reading(database, SELECT_ANSWERS).all().map(parseRetainedAnswer); +export function readAllSavedAnswers(database: DatabaseSync): SavedAnswer[] { + return reading(database, SELECT_ANSWERS).all().map(parseSavedAnswer); } /** @@ -97,14 +97,14 @@ export function insertPendingAnswer( } /** - * Mark this wait's retained answer consumed, and say whether it was this call + * Mark this wait's saved answer consumed, and say whether it was this call * that did it. * * The state is part of the statement rather than something read first: a second * consumption changes no rows and is told so, which is how the publication that * commits with it stays the only one. */ -export function consumeRetainedAnswer( +export function consumeSavedAnswer( database: DatabaseSync, suspensionId: string, consumedAt: string, @@ -113,7 +113,7 @@ export function consumeRetainedAnswer( return Number(changes) === 1; } -function parseRetainedAnswer(row: Record): RetainedAnswer { +function parseSavedAnswer(row: Record): SavedAnswer { return Object.freeze({ suspensionId: text(row, "suspension_id"), requestEventId: text(row, "request_event_id"), @@ -125,7 +125,7 @@ function parseRetainedAnswer(row: Record): RetainedAnswer { }); } -function state(row: Record): RetainedAnswerState { +function state(row: Record): SavedAnswerState { const value = text(row, "state"); if (value !== "pending" && value !== "consumed") { throw new WorkflowRecordMalformedError( diff --git a/packages/workflow/src/deno/artifact-frontier.ts b/packages/workflow/src/deno/artifact-frontier.ts index 2a601669a..56aed844a 100644 --- a/packages/workflow/src/deno/artifact-frontier.ts +++ b/packages/workflow/src/deno/artifact-frontier.ts @@ -12,7 +12,7 @@ * `readForkSource()` reads the prefix one checkpoint selects, because a fork * inherits history up to a point and writes its own beginning. An export * inherits nothing: it records what the run committed, so it reads every - * journal row, every retained root, and every other XMD-owned record the run + * journal row, every saved root, and every other XMD-owned record the run * holds. The two are deliberately separate readers over the same tables rather * than one reader with a mode, because "up to this checkpoint" and "everything" * differ in what they are allowed to leave out. @@ -33,19 +33,15 @@ import type { DetachedXmdArtifact, XmdArtifactJournalRow } from "./artifact/type import type { InheritedEventProvenance } from "../lifecycle/history.ts"; import { WorkflowRequestError } from "../storage/errors.ts"; import type { WorkflowRunRecord } from "../storage/record.ts"; -import { readAllRetainedAnswers } from "./answers.ts"; -import type { RetainedBlob, RetainedManifest } from "./fork-source.ts"; +import { readAllSavedAnswers } from "./answers.ts"; +import type { SavedBlob, SavedManifest } from "./fork-source.ts"; import { readForkLineage } from "./fork-write.ts"; -import { readRepositories, readRetainedRows, readWorktrees } from "./fork-source.ts"; +import { readRepositories, readSavedRows, readWorktrees } from "./fork-source.ts"; import { reading } from "./reading.ts"; import { readDocumentExecution, readRetrieval } from "./rows.ts"; import { readAllAgentSessions } from "./workspace/agent-sessions.ts"; import { bytes, integer } from "./workspace/manifest.ts"; -import { - currentWorkspaceRoot, - loadWorkspaceRoot, - retainedWorkspaceRoots, -} from "./workspace/root.ts"; +import { currentWorkspaceRoot, loadWorkspaceRoot, savedWorkspaceRoots } from "./workspace/root.ts"; const SELECT_EXECUTIONS = "SELECT * FROM document_executions ORDER BY sequence ASC"; @@ -64,7 +60,7 @@ export function readExportFrontier( path: string, ): Omit { const journal = readExportJournal(database); - const roots = retainedWorkspaceRoots(database).map((rootId) => + const roots = savedWorkspaceRoots(database).map((rootId) => loadWorkspaceRoot(database, rootId, path), ); const manifestHashes = new Set(roots.flatMap((root) => root.manifestHashes)); @@ -97,7 +93,7 @@ export function readExportFrontier( blobs: [...blobHashes].map((hash) => readBlob(database, hash, path)), repositories: readRepositories(database, path), worktrees: readWorktrees(database, path), - answers: readAllRetainedAnswers(database), + answers: readAllSavedAnswers(database), agentSessions: readAllAgentSessions(database), }; } @@ -119,13 +115,13 @@ function readLineageCreatedAt(database: DatabaseSync, path: string): string { const createdAt = row?.["created_at"]; if (typeof createdAt !== "string") { throw new WorkflowRequestError( - `the fork lineage retained at ${path} does not say when it was created.`, + `the fork lineage saved at ${path} does not say when it was created.`, ); } return createdAt; } -function readManifest(database: DatabaseSync, hash: string, path: string): RetainedManifest { +function readManifest(database: DatabaseSync, hash: string, path: string): SavedManifest { const row = reading( database, "SELECT hash, size, encoded, last_seen FROM vfs_manifests WHERE hex(hash) = upper(?)", @@ -143,7 +139,7 @@ function readManifest(database: DatabaseSync, hash: string, path: string): Retai }); } -function readBlob(database: DatabaseSync, hash: string, path: string): RetainedBlob { +function readBlob(database: DatabaseSync, hash: string, path: string): SavedBlob { const row = reading( database, `SELECT blob.hash AS hash, blob.size AS size, blob.last_seen AS last_seen, @@ -196,7 +192,7 @@ function readExportJournal(database: DatabaseSync): XmdArtifactJournalRow[] { inherited.set(eventId, Object.freeze({ sourceRunId, sourceEventId })); } - return readRetainedRows(database).map((row) => { + return readSavedRows(database).map((row) => { const provenance = inherited.get(row.eventId); return provenance === undefined ? row : { ...row, inherited: provenance }; }); diff --git a/packages/workflow/src/deno/artifact-inspection.ts b/packages/workflow/src/deno/artifact-inspection.ts index d2f0858ae..04cbc1cad 100644 --- a/packages/workflow/src/deno/artifact-inspection.ts +++ b/packages/workflow/src/deno/artifact-inspection.ts @@ -5,7 +5,7 @@ * is an artifact at all — recognition, manifest, inventory and every semantic * gate — and returns a detached, deep-frozen value or says why it will not. * This turns that value into the provider-neutral snapshot and history a - * retained run already answers with, and knows nothing about containers. + * saved run already answers with, and knows nothing about containers. * * ## No live ownership, at all * @@ -78,7 +78,7 @@ export function* historyArtifact(path: string): Operation(); for (const row of artifact.journal) { - // The retained bytes, through the parser that wrote them. A verified + // The saved bytes, through the parser that wrote them. A verified // container says these records are the ones the journal's own gate // filtered; what they *mean* is still this parser's answer, and a record it // refuses is history nobody can read rather than a row to skip. @@ -101,9 +101,9 @@ export function* historyArtifact(path: string): Operation root.rootId)), + savedRoots: new Set(artifact.roots.map((root) => root.rootId)), inherited, }), }), @@ -118,7 +118,7 @@ function identityOf(artifact: VerifiedXmdArtifact): WorkflowArtifactIdentity { * The last committed event and the root that row was associated with. * * The row's own root, never the current Workspace pointer. They are two - * retained facts and they disagree whenever the run's last event did not move + * saved facts and they disagree whenever the run's last event did not move * the Workspace — projecting one as the other would report a boundary the run * never had. */ diff --git a/packages/workflow/src/deno/artifact/manifest.ts b/packages/workflow/src/deno/artifact/manifest.ts index 7bea40c35..9a847e6ff 100644 --- a/packages/workflow/src/deno/artifact/manifest.ts +++ b/packages/workflow/src/deno/artifact/manifest.ts @@ -1,7 +1,7 @@ /** * The canonical inventory an XMD artifact is identified by. * - * The manifest names every semantic record and retained byte the artifact + * The manifest names every semantic record and saved byte the artifact * holds, and nothing about the file those bytes ended up in. Page size, free * space, insertion order, index layout and the path the file was written to are * all outside it — which is what makes the identity a statement about the diff --git a/packages/workflow/src/deno/artifact/read.ts b/packages/workflow/src/deno/artifact/read.ts index f08c6e1a2..936bd0e26 100644 --- a/packages/workflow/src/deno/artifact/read.ts +++ b/packages/workflow/src/deno/artifact/read.ts @@ -30,8 +30,8 @@ import { Err, Ok, type Operation, type Result } from "effection"; import type { Json } from "@executablemd/durable-streams"; import { type JsonObject, parseJsonValue } from "../../storage/members.ts"; import type { DocumentExecutionRecord, WorkflowRunRecord } from "../../storage/record.ts"; -import type { RetainedAnswer } from "../answers.ts"; -import type { RetainedBlob, RetainedManifest } from "../fork-source.ts"; +import type { SavedAnswer } from "../answers.ts"; +import type { SavedBlob, SavedManifest } from "../fork-source.ts"; import type { AgentSessionRecord } from "../workspace/agent-sessions.ts"; import type { StoredWorkspaceRoot } from "../workspace/manifest.ts"; import { @@ -73,7 +73,7 @@ import { type XmdArtifactEncoding, type XmdArtifactJournalRow, } from "./types.ts"; -import type { RetainedDefinitionSources } from "../../lifecycle/source.ts"; +import type { SavedDefinitionSources } from "../../lifecycle/source.ts"; import type { WorkflowDefinition } from "../../storage/source-bundle.ts"; const SELECT_HEADER_VERSION = "SELECT artifact_version FROM xmd_artifact_header WHERE id = 1"; @@ -126,8 +126,8 @@ interface SealedContent { * Everything the rest of recognition needs is in memory before this returns, so * no later gate is reading through a handle and nothing a caller could observe * outlives the file being open. It is also what lets the semantic pass be an - * operation: judging a retained answer means compiling the schema its wait - * retained, and that cannot happen inside a synchronous SQLite transaction. + * operation: judging a saved answer means compiling the schema its wait + * saved, and that cannot happen inside a synchronous SQLite transaction. */ function detach(path: string): SealedContent { let database: DatabaseSync; @@ -248,7 +248,7 @@ function* recognized(sealed: SealedContent, path: string): Operation, path: string, kind: string): W } /** - * The records in their retained order, refusing any order with a hole in it. + * The records in their saved order, refusing any order with a hole in it. * * Position is carried by each record rather than implied by how the container * happened to return its rows, because the physical order of a generic content @@ -633,7 +628,7 @@ function ordered( if (row.position !== index) { throw new XmdArtifactInventoryError( path, - `its ${kind} records do not occupy one contiguous retained order`, + `its ${kind} records do not occupy one contiguous saved order`, ); } } @@ -711,7 +706,7 @@ function decodeRun(inventory: Inventory, path: string): WorkflowRunRecord { kind, ); const reason = stopReason(parsed, path, kind); - const retained = definition(parsed, path, kind); + const saved = definition(parsed, path, kind); const shared = { runId: parseRunId(parsed.get("runId"), "$.runId", failing(path, kind)), props: parseJsonObject(parsed.get("props"), "$.props", failing(path, kind)), @@ -728,7 +723,7 @@ function decodeRun(inventory: Inventory, path: string): WorkflowRunRecord { } const record: WorkflowRunRecord = { runId: shared.runId, - definition: retained, + definition: saved, props: shared.props, status: shared.status, createdAt: shared.createdAt, @@ -826,7 +821,7 @@ function decodeJournal(inventory: Inventory, path: string): readonly XmdArtifact try { readEventSource(event.value); } catch { - // The live parser names the retained field and its shape; what it read is + // The live parser names the saved field and its shape; what it read is // filtered history and stays out of the sentence that reaches a terminal. throw new XmdArtifactRecordError( path, @@ -933,7 +928,7 @@ function hashes( return Object.freeze(list); } -function decodeManifests(inventory: Inventory, path: string): readonly RetainedManifest[] { +function decodeManifests(inventory: Inventory, path: string): readonly SavedManifest[] { const kind = "dofs-manifest"; return Object.freeze( inventory.identities(kind).map((identity) => { @@ -960,7 +955,7 @@ function decodeManifests(inventory: Inventory, path: string): readonly RetainedM ); } -function decodeBlobs(inventory: Inventory, path: string): readonly RetainedBlob[] { +function decodeBlobs(inventory: Inventory, path: string): readonly SavedBlob[] { const kind = "dofs-blob"; return Object.freeze( inventory.identities(kind).map((identity) => { @@ -987,7 +982,7 @@ function decodeBlobs(inventory: Inventory, path: string): readonly RetainedBlob[ ); } -function decodeRepositories(inventory: Inventory, path: string): readonly RetainedRepository[] { +function decodeRepositories(inventory: Inventory, path: string): readonly SavedRepository[] { const kind = "workspace-repository"; return Object.freeze( inventory.identities(kind).map((identity) => { @@ -1034,7 +1029,7 @@ function decodeRepositories(inventory: Inventory, path: string): readonly Retain ); } -function decodeWorktrees(inventory: Inventory, path: string): readonly RetainedWorktree[] { +function decodeWorktrees(inventory: Inventory, path: string): readonly SavedWorktree[] { const kind = "workspace-worktree"; return Object.freeze( inventory.identities(kind).map((identity) => { @@ -1106,7 +1101,7 @@ function checkoutPath(parsed: Map, path: string, kind: string): return value; } -function decodeAnswers(inventory: Inventory, path: string): readonly RetainedAnswer[] { +function decodeAnswers(inventory: Inventory, path: string): readonly SavedAnswer[] { const kind = "suspension-answer"; return Object.freeze( inventory.identities(kind).map((identity) => { @@ -1153,8 +1148,8 @@ function decodeAnswers(inventory: Inventory, path: string): readonly RetainedAns ); } -/** Whether a retained answer is still waiting to be published, or already was. */ -function answerState(value: string, path: string, kind: string): RetainedAnswerState { +/** Whether a saved answer is still waiting to be published, or already was. */ +function answerState(value: string, path: string, kind: string): SavedAnswerState { if (value === "pending" || value === "consumed") { return value; } @@ -1219,12 +1214,12 @@ function decodeDefinitionSources( inventory: Inventory, path: string, run: WorkflowRunRecord, -): RetainedDefinitionSources { +): SavedDefinitionSources { return decodeSourceBundle(inventory, path, run.definition); } /** - * The retained bytes this artifact carries, one pair per logical path. + * The saved bytes this artifact carries, one pair per logical path. * * Each pair's natural identity is the canonical JSON string of its path, and * both halves are consumed under it: an entry whose own `path` is not the @@ -1239,9 +1234,9 @@ function decodeSourceBundle( inventory: Inventory, path: string, definition: WorkflowDefinition, -): RetainedDefinitionSources { +): SavedDefinitionSources { const kind = "definition-source-entry"; - const retained = new Map(definition.sources.map((source) => [source.path, source])); + const saved = new Map(definition.sources.map((source) => [source.path, source])); const sources = inventory.identities(kind).map((identity) => { const parsed = members( structured(inventory.take(kind, identity), path), @@ -1260,7 +1255,7 @@ function decodeSourceBundle( // are checked against the descriptor too, so agreeing with it is what makes // the entry, the content and the definition one statement rather than three // a re-sealed artifact could move independently. - const entry = retained.get(logical); + const entry = saved.get(logical); if (entry === undefined) { throw new XmdArtifactInventoryError( path, @@ -1371,7 +1366,7 @@ function verifyLifecycle( } /** - * Every retained byte, held to the identity it is stored under. + * Every saved byte, held to the identity it is stored under. * * Sizes and hashes are recomputed rather than believed: the manifest already * proved the bytes are the ones that were sealed, and this proves the bytes are @@ -1450,7 +1445,7 @@ function verifyRoots( reject("a Workspace root names a DOFS manifest this artifact does not hold"); } if (entry.size !== manifest.size) { - reject("a retained file size differs from its DOFS manifest"); + reject("a saved file size differs from its DOFS manifest"); } declared.set(entry.manifest, manifest); } @@ -1477,10 +1472,10 @@ function verifyRoots( } if (referencedManifests.size !== contents.manifests.length) { - reject("it holds DOFS manifests no retained Workspace root needs"); + reject("it holds DOFS manifests no saved Workspace root needs"); } if (referencedBlobs.size !== contents.blobs.length) { - reject("it holds DOFS blobs no retained Workspace root needs"); + reject("it holds DOFS blobs no saved Workspace root needs"); } } @@ -1510,7 +1505,7 @@ function verifyCheckouts(contents: XmdArtifactContents, reject: Reject): void { } } -/** One retained Yield, at the durable position its coroutine gave it. */ +/** One saved Yield, at the durable position its coroutine gave it. */ interface DurableYield { readonly eventId: string; readonly coroutineId: string; @@ -1518,7 +1513,7 @@ interface DurableYield { readonly index: number; readonly description: EffectDescription; readonly result: Result; - /** Whether the retained run wrote this row, rather than inheriting it. */ + /** Whether the saved run wrote this row, rather than inheriting it. */ readonly own: boolean; } @@ -1529,12 +1524,12 @@ interface AnswerPublication { } /** - * Every retained answer, authenticated against the retained history of its + * Every saved answer, authenticated against the saved history of its * corresponding request. * * A row naming an event the journal holds proves nothing on its own. What has * to be true is that the named event *is* the wait, that it is *this* wait, that - * the request it retained is the one the value was judged against, that the + * the request it saved is the one the value was judged against, that the * value could still end that wait, and — when the row says the wait is over — * that the journal published exactly that value at exactly the position a * publication occupies. @@ -1569,9 +1564,9 @@ function* verifySuspensions( } const named = entry.description["name"]; if (typeof named !== "string" || named !== entry.description["suspensionId"]) { - reject("a retained suspension request does not name one wait"); + reject("a saved suspension request does not name one wait"); } - // Every request the retained run authored, whether or not an answer row or + // Every request the saved run authored, whether or not an answer row or // a publication ever reaches it. A wait's identity is derived from the run // and the exact durable position, and nothing else — a caller supplies // neither — so a request standing at a position that derives some other @@ -1581,7 +1576,7 @@ function* verifySuspensions( // at that run's position, and re-deriving it here would refuse every fork // for having a history. if (entry.own && suspensionId(contents.run.runId, entry) !== named) { - reject("a retained suspension request names a wait its own position does not derive"); + reject("a saved suspension request names a wait its own position does not derive"); } requests.set(entry.eventId, entry); } @@ -1624,7 +1619,7 @@ function* verifySuspensions( const request = requests.get(answer.requestEventId); if (request === undefined) { - reject("a suspension answer names an event that is not a retained request for a wait"); + reject("a suspension answer names an event that is not a saved request for a wait"); } if ( request.description["name"] !== answer.suspensionId || @@ -1645,14 +1640,14 @@ function* verifySuspensions( }); } catch { // The live parser's sentence describes a request a document offered; what - // this artifact holds is a retained one, and the value stays out of it. - reject("a retained suspension request does not describe a wait"); + // this artifact holds is a saved one, and the value stays out of it. + reject("a saved suspension request does not describe a wait"); } if (suspensionRequestFingerprint(wait) !== answer.requestFingerprint) { reject("a suspension answer was delivered against a request this artifact does not hold"); } - yield* judgeRetainedAnswer(wait, answer.answer, reject); + yield* judgeSavedAnswer(wait, answer.answer, reject); const publication = published.get(answer.suspensionId); if (answer.state === "pending") { @@ -1676,7 +1671,7 @@ function* verifySuspensions( } // Presence, and never normalized. A publication carrying no value at all is // not a publication of `null`: reading it as one would let a wait that - // ended with nothing stand as evidence for a wait whose retained answer is + // ended with nothing stand as evidence for a wait whose saved answer is // null, and those are different histories. The member is asked for by name // — a record parsed out of JSON cannot hold an explicit `undefined`, so // absence is the only way past this. @@ -1695,7 +1690,7 @@ function positionKey(coroutineId: string, index: number): string { } /** - * Every retained Yield, with the durable position its coroutine gave it. + * Every saved Yield, with the durable position its coroutine gave it. * * A position is a count of that coroutine's settled yields, so it is recovered * by counting them in append order. A Close terminates a coroutine and settles @@ -1726,14 +1721,14 @@ function durableYields(contents: XmdArtifactContents, path: string): readonly Du } /** - * The value, judged by the schema the wait itself retained. + * The value, judged by the schema the wait itself saved. * * The same compilation a delivery and an `` use, so what this artifact * accepts as an answer is exactly what a live run would have. The refusal says * that it does not satisfy the schema and never which part of it failed: the - * issues quote the value, and a value is retained content. + * issues quote the value, and a value is saved content. */ -function* judgeRetainedAnswer( +function* judgeSavedAnswer( wait: WorkflowSuspensionRequest, answer: Json, reject: Reject, @@ -1743,14 +1738,14 @@ function* judgeRetainedAnswer( const prepared = yield* prepareElicitation(wait.responseSchema, "artifact answer"); issues = validateParsed(prepared.validate, answer); } catch { - reject("a retained response schema cannot judge the answer stored against it"); + reject("a saved response schema cannot judge the answer stored against it"); } if (issues.length > 0) { - reject("a retained answer does not satisfy the response schema its wait retained"); + reject("a saved answer does not satisfy the response schema its wait saved"); } } -/** One retained record, as the durable event it describes. */ +/** One saved record, as the durable event it describes. */ function parseJournalEvent(record: string, path: string): DurableEvent { const parsed = parseDurableEvent(record); if (!parsed.ok) { @@ -1774,7 +1769,7 @@ function verifyAgentSessions(contents: XmdArtifactContents, reject: Reject): voi } keys.add(session.sessionKey); if (agentSessionKey(session) !== session.sessionKey) { - reject("an Agent session mapping is retained under a key its identity does not derive"); + reject("an Agent session mapping is saved under a key its identity does not derive"); } } } @@ -1795,7 +1790,7 @@ function* verifyDefinitionSources(contents: XmdArtifactContents, reject: Reject) return; } if (sources.length !== definition.sources.length) { - reject("it does not carry exactly one source pair per retained path"); + reject("it does not carry exactly one source pair per saved path"); return; } @@ -1893,7 +1888,7 @@ export function partitionXmdArtifactEntries( * * Nothing at all is merged legacy V1. No marker was ever stored for it and none * is invented here: a Prompt-contributing session that carries no - * classification is preserved exactly as it was retained, and a caller receives + * classification is preserved exactly as it was saved, and a caller receives * no member rather than an empty one it could mistake for a decision. */ export function decodeXmdArtifactAgentEvidence( @@ -2064,14 +2059,14 @@ function decodeAssociations( ); } -/** One retained Prompt, as the journal holds it and the live parser reads it. */ -interface RetainedPrompt { +/** One saved Prompt, as the journal holds it and the live parser reads it. */ +interface SavedPrompt { readonly eventId: string; readonly record: PromptRecord; } /** - * Whether this artifact classifies every Agent session it retained a Prompt for. + * Whether this artifact classifies every Agent session it saved a Prompt for. * * Asked only of a finalized artifact, and only once the stored manifest and the * artifact identity have already been compared — so what is being interpreted @@ -2091,10 +2086,10 @@ export function verifyXmdArtifactAgentEvidence( const reject: Reject = inventoryFailure(path); const sessions = new Map(contents.agentSessions.map((session) => [session.sessionKey, session])); - const prompts = new Map(); - for (const prompt of retainedPrompts(contents, path, reject)) { + const prompts = new Map(); + for (const prompt of savedPrompts(contents, path, reject)) { if (!sessions.has(prompt.record.sessionKey)) { - reject("a retained Agent Prompt names a session mapping this artifact does not hold"); + reject("a saved Agent Prompt names a session mapping this artifact does not hold"); } const group = prompts.get(prompt.record.sessionKey) ?? []; group.push(prompt); @@ -2130,19 +2125,19 @@ export function verifyXmdArtifactAgentEvidence( reject("an Agent portability record disagrees with the session mapping it classifies"); } - const retained = prompts.get(record.sessionKey); - if (retained === undefined) { - reject("an Agent portability record classifies a session that retained no Prompt"); + const saved = prompts.get(record.sessionKey); + if (saved === undefined) { + reject("an Agent portability record classifies a session that saved no Prompt"); return; } - verifyAssociations(record, retained, reject); + verifyAssociations(record, saved, reject); const covered = record.associations.map((association) => association.eventId); const exactCoverage = - retained.every((prompt) => prompt.record.status === "completed") && + saved.every((prompt) => prompt.record.status === "completed") && sameStrings( covered, - retained.map((prompt) => prompt.eventId), + saved.map((prompt) => prompt.eventId), ); const bundle = bundles.get(record.sessionKey); @@ -2154,7 +2149,7 @@ export function verifyXmdArtifactAgentEvidence( reject("a portable Agent session names a source session the mapping never asserted"); } if (!exactCoverage) { - reject("a portable Agent session does not associate every Prompt it retained"); + reject("a portable Agent session does not associate every Prompt it saved"); } if (bundle === undefined) { reject("a portable Agent session carries no bundle"); @@ -2173,18 +2168,18 @@ export function verifyXmdArtifactAgentEvidence( reject("an unavailable Agent session carries bundle bytes"); } if (record.reason === "provider-capability-unavailable" && !exactCoverage) { - reject("an Agent session with incomplete retained Prompt evidence claims the wrong reason"); + reject("an Agent session with incomplete saved Prompt evidence claims the wrong reason"); } if (record.reason === "checkpoint-token-unavailable" && exactCoverage) { reject( - "an Agent session whose retained Prompts are complete and covered claims the wrong reason", + "an Agent session whose saved Prompts are complete and covered claims the wrong reason", ); } } for (const sessionKey of prompts.keys()) { if (!classified.has(sessionKey)) { - reject("a session that retained a Prompt carries no Agent portability record"); + reject("a session that saved a Prompt carries no Agent portability record"); } } for (const sessionKey of bundles.keys()) { @@ -2195,7 +2190,7 @@ export function verifyXmdArtifactAgentEvidence( } /** - * The associations one record names, held to the Prompts the session retained. + * The associations one record names, held to the Prompts the session saved. * * Distinct, in journal append order, each naming a Prompt of *this* session * that completed. A dangling, foreign, reordered, repeated, non-Prompt or @@ -2204,15 +2199,15 @@ export function verifyXmdArtifactAgentEvidence( */ function verifyAssociations( record: XmdArtifactAgentPortability, - retained: readonly RetainedPrompt[], + saved: readonly SavedPrompt[], reject: Reject, ): void { - const positions = new Map(retained.map((prompt, index) => [prompt.eventId, index])); + const positions = new Map(saved.map((prompt, index) => [prompt.eventId, index])); const seen = new Set(); let previous = -1; for (const association of record.associations) { if (seen.has(association.eventId)) { - reject("an Agent portability record names one retained Prompt twice"); + reject("an Agent portability record names one saved Prompt twice"); } seen.add(association.eventId); const at = positions.get(association.eventId); @@ -2224,25 +2219,25 @@ function verifyAssociations( reject("an Agent portability record's associations are not in journal append order"); } previous = at; - if (retained[at]?.record.status !== "completed") { + if (saved[at]?.record.status !== "completed") { reject("an Agent portability record associates a Prompt that did not complete"); } } } /** - * Every retained Prompt, in journal append order. + * Every saved Prompt, in journal append order. * * A finalized artifact classifies what its Prompts say, so a Prompt whose * durable result is a failure or is not a Prompt record at all is damage rather * than a session with nothing to classify. */ -function retainedPrompts( +function savedPrompts( contents: XmdArtifactContents, path: string, reject: Reject, -): readonly RetainedPrompt[] { - const prompts: RetainedPrompt[] = []; +): readonly SavedPrompt[] { + const prompts: SavedPrompt[] = []; for (const row of contents.journal) { const event = parseJournalEvent(row.record, path); if (event.type !== "yield" || event.description.type !== AGENT_PROMPT) { @@ -2251,7 +2246,7 @@ function retainedPrompts( const result = event.result; const record = result.status === "ok" ? parsePromptRecord(result.value) : undefined; if (record === undefined) { - reject("a retained Agent Prompt holds no Prompt record"); + reject("a saved Agent Prompt holds no Prompt record"); return prompts; } prompts.push(Object.freeze({ eventId: row.eventId, record })); diff --git a/packages/workflow/src/deno/artifact/schema.ts b/packages/workflow/src/deno/artifact/schema.ts index e1d557cb7..8029398c2 100644 --- a/packages/workflow/src/deno/artifact/schema.ts +++ b/packages/workflow/src/deno/artifact/schema.ts @@ -262,7 +262,7 @@ function checkIntegrity(database: DatabaseSync, path: string): void { * Version 1 declares no foreign key, so this can only ever report nothing on a * container whose schema has just been recognized as version 1's. It is asked * anyway because it costs one statement and because the reference invariants - * that *can* fail — an event naming a root nobody retained, a root naming + * that *can* fail — an event naming a root nobody saved, a root naming * content nobody stored — are semantic rather than physical, and are checked * where the records themselves are parsed. */ diff --git a/packages/workflow/src/deno/artifact/types.ts b/packages/workflow/src/deno/artifact/types.ts index 0532b2e4a..3f0f0c764 100644 --- a/packages/workflow/src/deno/artifact/types.ts +++ b/packages/workflow/src/deno/artifact/types.ts @@ -11,7 +11,7 @@ * Every member is a value the live workflow contract already retains, spelled * with the type that contract already uses. Restating them here would be a * second definition of the same records that could disagree with the first, so - * the retained shapes are imported and only the two things the live contract + * the saved shapes are imported and only the two things the live contract * has no name for — the artifact frontier and the definition source closure — * are declared. * @@ -29,15 +29,10 @@ import type { InheritedEventProvenance } from "../../lifecycle/history.ts"; import type { DocumentExecutionRecord, WorkflowRunRecord } from "../../storage/record.ts"; import type { XmdArtifactFrontier } from "../../lifecycle/export.ts"; import type { AgentSessionRecord } from "../workspace/agent-sessions.ts"; -import type { RetainedAnswer } from "../answers.ts"; -import type { - RetainedBlob, - RetainedManifest, - RetainedRepository, - RetainedWorktree, -} from "../fork-source.ts"; +import type { SavedAnswer } from "../answers.ts"; +import type { SavedBlob, SavedManifest, SavedRepository, SavedWorktree } from "../fork-source.ts"; import type { StoredWorkspaceRoot } from "../workspace/manifest.ts"; -import type { RetainedDefinitionSources } from "../../lifecycle/source.ts"; +import type { SavedDefinitionSources } from "../../lifecycle/source.ts"; /** The boundary the lifecycle chose, as the shape every record here is about. */ export type { XmdArtifactFrontier }; @@ -51,9 +46,9 @@ export interface XmdArtifactForkLineage { } /** - * One retained journal row, and what the run knows about where it came from. + * One saved journal row, and what the run knows about where it came from. * - * `record` is the exact retained NDJSON text, never a re-encoding of a parsed + * `record` is the exact saved NDJSON text, never a re-encoding of a parsed * event: the journal's security gate already filtered these bytes, and a * container that normalized them would be retaining something no gate had seen. * The authored source position lives inside those bytes and is validated from @@ -77,10 +72,10 @@ export interface XmdArtifactJournalRow { */ /** - * One retained Prompt event, and the provider checkpoint token taken at it. + * One saved Prompt event, and the provider checkpoint token taken at it. * * A token is opaque: it is whatever the provider that issued it can later - * present, and nothing here derives, normalizes or infers one. It is retained + * present, and nothing here derives, normalizes or infers one. It is saved * evidence rather than a credential — it names a place in a * conversation, not permission to reach the host that had it. */ @@ -116,7 +111,7 @@ export interface XmdArtifactAgentPortabilityBase { /** * A session a fork could continue, and the bundle that would continue it. * - * The bundled identity is retained separately from the source identity because + * The bundled identity is saved separately from the source identity because * detached bytes may name a transported session rather than the live one they * were captured from. */ @@ -134,7 +129,7 @@ export interface XmdArtifactPortableAgentSession extends XmdArtifactAgentPortabi /** * A session no bundle was sealed for, and which of the two reasons applies. * - * `checkpoint-token-unavailable` is intrinsic: the run's own retained evidence + * `checkpoint-token-unavailable` is intrinsic: the run's own saved evidence * is incomplete, and no other host's capability can repair it. * `provider-capability-unavailable` is the export decision — every Prompt was * completed and covered, and nothing declared a way to capture the session. @@ -183,11 +178,11 @@ export interface XmdArtifactContents { readonly lineage?: XmdArtifactForkLineage; readonly journal: readonly XmdArtifactJournalRow[]; readonly roots: readonly StoredWorkspaceRoot[]; - readonly manifests: readonly RetainedManifest[]; - readonly blobs: readonly RetainedBlob[]; - readonly repositories: readonly RetainedRepository[]; - readonly worktrees: readonly RetainedWorktree[]; - readonly answers: readonly RetainedAnswer[]; + readonly manifests: readonly SavedManifest[]; + readonly blobs: readonly SavedBlob[]; + readonly repositories: readonly SavedRepository[]; + readonly worktrees: readonly SavedWorktree[]; + readonly answers: readonly SavedAnswer[]; readonly agentSessions: readonly AgentSessionRecord[]; /** Absent unless the artifact classifies its Prompt-contributing sessions. */ readonly agentEvidence?: XmdArtifactAgentEvidence; @@ -199,7 +194,7 @@ export interface XmdArtifactContents { * so the writer selects a format from what it was handed rather than from a * superset both versions could be read out of. */ - readonly definition: RetainedDefinitionSources; + readonly definition: SavedDefinitionSources; } /** diff --git a/packages/workflow/src/deno/definition-source.ts b/packages/workflow/src/deno/definition-source.ts index c4d67f7fc..b3e53ac3f 100644 --- a/packages/workflow/src/deno/definition-source.ts +++ b/packages/workflow/src/deno/definition-source.ts @@ -3,16 +3,16 @@ * * A source bundle's authoritative content lives in this database and nowhere * else. So writing it is one set of rows inside the creation transaction, and - * reading it is a total re-derivation: every retained length, every source hash + * reading it is a total re-derivation: every saved length, every source hash * and the bundle hash itself are recomputed from the BLOBs before anything is - * handed back. A retained identity nobody can reproduce is not evidence of the + * handed back. A saved identity nobody can reproduce is not evidence of the * definition it names. * * ## Missing and corrupt are different answers * * A manifest entry or a blob the store does not contain is absence: the run's * content is gone, and there is nothing here to repair. A path, a length, a - * hash, a mapping or a bundle hash that disagrees is damage: what is retained + * hash, a mapping or a bundle hash that disagrees is damage: what is saved * is there and no longer describes itself. Operators act on the two * differently, and neither is repaired by falling back to the original file, * the optional provenance, or the legacy Git reader. @@ -27,7 +27,7 @@ import type { DatabaseSync } from "node:sqlite"; import { Err, Ok, type Operation, type Result } from "effection"; -import type { RetainedDefinitionSources, RetainedSource } from "../lifecycle/source.ts"; +import type { SavedDefinitionSources, SavedSource } from "../lifecycle/source.ts"; import { WorkflowDefinitionCorruptError, WorkflowDefinitionSourceMissingError, @@ -75,13 +75,13 @@ export function writeDefinitionSources( } } -/** One retained manifest row, as SQLite hands it back. */ +/** One saved manifest row, as SQLite hands it back. */ interface ManifestRow { readonly path: string; readonly sourceHash: string; } -/** One retained blob, as SQLite hands it back. */ +/** One saved blob, as SQLite hands it back. */ interface BlobRow { readonly sourceHash: string; readonly byteLength: number; @@ -89,13 +89,13 @@ interface BlobRow { } /** Everything the source store holds, read inside the caller's transaction. */ -export interface RetainedSourceRows { +export interface SavedSourceRows { readonly manifest: readonly ManifestRow[]; readonly blobs: readonly BlobRow[]; } /** Read the whole source store, without deciding anything about it yet. */ -export function readDefinitionSourceRows(database: DatabaseSync): RetainedSourceRows { +export function readDefinitionSourceRows(database: DatabaseSync): SavedSourceRows { return { manifest: reading(database, SELECT_SOURCES) .all() @@ -114,21 +114,21 @@ export function readDefinitionSourceRows(database: DatabaseSync): RetainedSource } /** - * The retained source this descriptor names, re-derived from what is stored. + * The saved source this descriptor names, re-derived from what is stored. * * Every check the issue settles, in the order an operator can act on: the * manifest must be exactly the descriptor's entries, every referenced blob must - * exist, both retained lengths must equal the content's own, recomputing each + * exist, both saved lengths must equal the content's own, recomputing each * blob's hash must produce its key, no blob may be unreferenced, and the bundle - * hash must be what the retained manifest and mapping produce. + * hash must be what the saved manifest and mapping produce. * * Nothing partial is ever returned. A closure with one source missing is not a * smaller closure, it is a different definition. */ -export function* verifyRetainedSources( +export function* verifySavedSources( definition: WorkflowDefinition, - rows: RetainedSourceRows, -): Operation> { + rows: SavedSourceRows, +): Operation> { const stored = new Map(rows.manifest.map((row) => [row.path, row.sourceHash])); if (stored.size !== rows.manifest.length) { return Err(new WorkflowDefinitionCorruptError("it retains one logical path more than once")); @@ -143,35 +143,31 @@ export function* verifyRetainedSources( } const referenced = new Set(); - const sources: RetainedSource[] = []; + const sources: SavedSource[] = []; for (const entry of definition.sources) { - const retainedHash = stored.get(entry.path); - if (retainedHash === undefined) { + const savedHash = stored.get(entry.path); + if (savedHash === undefined) { return Err(new WorkflowDefinitionSourceMissingError()); } - if (retainedHash !== entry.sourceHash) { + if (savedHash !== entry.sourceHash) { return Err( - new WorkflowDefinitionCorruptError( - "a retained source names content the descriptor does not", - ), + new WorkflowDefinitionCorruptError("a saved source names content the descriptor does not"), ); } - const blob = blobs.get(retainedHash); + const blob = blobs.get(savedHash); if (blob === undefined) { return Err(new WorkflowDefinitionSourceMissingError()); } - referenced.add(retainedHash); + referenced.add(savedHash); if (blob.byteLength !== entry.byteLength || blob.content.byteLength !== entry.byteLength) { return Err( - new WorkflowDefinitionCorruptError("a retained source is not the length it declares"), + new WorkflowDefinitionCorruptError("a saved source is not the length it declares"), ); } const recomputed = yield* sourceContentHash(blob.content); if (recomputed !== entry.sourceHash) { - return Err( - new WorkflowDefinitionCorruptError("a retained source is not the content it names"), - ); + return Err(new WorkflowDefinitionCorruptError("a saved source is not the content it names")); } sources.push(Object.freeze({ path: entry.path, bytes: Uint8Array.from(blob.content) })); } diff --git a/packages/workflow/src/deno/delivery.ts b/packages/workflow/src/deno/delivery.ts index 5f175ec3f..f2d2c830e 100644 --- a/packages/workflow/src/deno/delivery.ts +++ b/packages/workflow/src/deno/delivery.ts @@ -3,16 +3,16 @@ * * Delivery reaches a run's file the same way storage does — the SHA-256 of the * public run id beneath the authorized root — and then does the least a durable - * answer can be retained with. It takes no executor lock, so a run somebody is + * answer can be saved with. It takes no executor lock, so a run somebody is * about to resume, or is resuming right now, can still be answered; what serializes * the write is the connection and SQLite, not lifecycle ownership. * * ## Everything is checked before anything is written * - * The run has to be `suspended`, its stop reason has to name a retained + * The run has to be `suspended`, its stop reason has to name a saved * `suspension_request` event, and that event has to be the wait the caller * named. The value is then judged against the response schema that request - * retained, and scanned for credentials. Only after all of that does a + * saved, and scanned for credentials. Only after all of that does a * transaction open — and it asks the same questions again, inside the * transaction, because everything above it was read from a snapshot somebody * else could have moved on from. @@ -54,7 +54,7 @@ import { WorkflowStorageError, } from "../storage/errors.ts"; import { canonicalJson, type WorkflowRunRecord } from "../storage/record.ts"; -import { insertPendingAnswer, readRetainedAnswer } from "./answers.ts"; +import { insertPendingAnswer, readSavedAnswer } from "./answers.ts"; import type { RunConnection, RunTransaction, WorkflowRunConnections } from "./connections.ts"; import { useWorkflowRunConnections } from "./connections.ts"; import { readRunRow } from "./database.ts"; @@ -100,8 +100,8 @@ interface CheckedDelivery { readonly secretDetection: boolean; } -/** The wait a run is at, read from what it retained. */ -interface RetainedWait { +/** The wait a run is at, read from what it saved. */ +interface SavedWait { readonly record: WorkflowRunRecord; readonly requestEventId: string; readonly request: WorkflowSuspensionRequest; @@ -129,12 +129,12 @@ function* deliverAnswer( const connection = yield* connections.at(path); - const waiting = yield* scoped(function* (): Operation> { + const waiting = yield* scoped(function* (): Operation> { yield* connection.lock.hold(); try { return Ok( readTransaction(connection.database, () => - retainedWait(connection, path, runId, suspensionId), + savedWait(connection, path, runId, suspensionId), ), ); } catch (error) { @@ -172,16 +172,16 @@ function* deliverAnswer( * * Recognition first, so a file that is not this run's version-1 database is * refused as itself. Then the run's own account of why it stopped: a status of - * `suspended` whose stop reason names a retained request event. Anything else — + * `suspended` whose stop reason names a saved request event. Anything else — * a completed run, a cancelled one, a run stopped for a different reason, a * request event for another wait — is a run this value does not answer. */ -function retainedWait( +function savedWait( connection: RunConnection, path: string, runId: string, suspensionId: string, -): RetainedWait { +): SavedWait { const { database } = connection; verifySchema(database, path, connection.dofs); const record = readRunRow(database, path); @@ -219,7 +219,7 @@ function retainedWait( ); } - // Parsed rather than read: the retained description is reached through a + // Parsed rather than read: the saved description is reached through a // public durable operation, so what it holds is a claim about a request until // this boundary has walked it — and a schema nothing could validate against // must not be the schema a value is judged by. @@ -228,10 +228,10 @@ function retainedWait( responseSchema: event.description.responseSchema, }); - const retained = readRetainedAnswer(database, suspensionId); - if (retained !== undefined) { + const saved = readSavedAnswer(database, suspensionId); + if (saved !== undefined) { throw new WorkflowAnswerDeliveryError( - retained.state === "pending" + saved.state === "pending" ? `workflow run ${runId} already has an answer waiting for ${suspensionId}. One value ` + "ends one wait; resume the run to deliver it." : `the wait ${suspensionId} in workflow run ${runId} has already been answered.`, @@ -247,7 +247,7 @@ function retainedWait( } /** - * The value, judged by the schema the wait retained. + * The value, judged by the schema the wait saved. * * The same compilation `` uses, so what a document may receive here is * exactly what it may receive there. The refusal names where the value went @@ -255,7 +255,7 @@ function retainedWait( * publish it in a place nothing filters. */ function* judgeAnswer( - waiting: RetainedWait, + waiting: SavedWait, suspensionId: string, value: Json, ): Operation> { @@ -266,7 +266,7 @@ function* judgeAnswer( } catch (error) { return Err( new WorkflowAnswerDeliveryError( - `the response schema retained for ${suspensionId} cannot judge an answer: ` + + `the response schema saved for ${suspensionId} cannot judge an answer: ` + (error instanceof Error ? error.message : String(error)), ), ); @@ -282,16 +282,16 @@ function* judgeAnswer( return Err( new WorkflowAnswerDeliveryError( `the value offered to ${suspensionId} does not satisfy the response schema that wait ` + - `retained: ${described}.`, + `saved: ${described}.`, ), ); } /** - * Cross the same gate a durable event crosses, before anything is retained. + * Cross the same gate a durable event crosses, before anything is saved. * * Secret detection is attached to journal persistence, and this value is - * heading for both retained state and a journal event — so both are scanned, + * heading for both saved state and a journal event — so both are scanned, * in the framing each will have. A credential that reached a run's file has * already leaked, whatever the resume that would have published it does next. * @@ -299,7 +299,7 @@ function* judgeAnswer( * fingerprints mean nothing outside this call and are not reported. */ function* scanDelivery( - waiting: RetainedWait, + waiting: SavedWait, suspensionId: string, value: Json, ): Operation> { @@ -319,7 +319,7 @@ function* scanDelivery( } catch (error) { return Err( new WorkflowAnswerDeliveryError( - "secret detection could not scan this answer, so it was not retained: " + + "secret detection could not scan this answer, so it was not saved: " + (error instanceof Error ? error.message : String(error)), ), ); @@ -362,7 +362,7 @@ function describeDetection(findings: readonly SecretFinding[]): string { .filter((description, index, all) => all.indexOf(description) === index) .join(", "); return ( - `${detected.name}: this answer was not retained because secret detection matched it: ` + + `${detected.name}: this answer was not saved because secret detection matched it: ` + `${where}. Neither the value nor the match is recorded. Disable detection for this ` + "delivery with --no-secret-detection only when the value is known not to be a credential." ); @@ -394,10 +394,10 @@ function retain( throw error; } try { - const waiting = retainedWait(connection, path, runId, suspensionId); + const waiting = savedWait(connection, path, runId, suspensionId); if (waiting.fingerprint !== fingerprint) { throw new WorkflowAnswerDeliveryError( - `the request retained for ${suspensionId} changed while this answer was being judged, ` + + `the request saved for ${suspensionId} changed while this answer was being judged, ` + "so the value was judged against a schema this run no longer waits on.", ); } @@ -474,7 +474,7 @@ function checkDelivery(offered: WorkflowAnswerDelivery): Result if (value === undefined) { return Err( new WorkflowRequestError( - "an answer is retained in this run's storage, so it must be JSON this run can store.", + "an answer is saved in this run's storage, so it must be JSON this run can store.", ), ); } diff --git a/packages/workflow/src/deno/fork-source.ts b/packages/workflow/src/deno/fork-source.ts index 31cdc2f98..d88886122 100644 --- a/packages/workflow/src/deno/fork-source.ts +++ b/packages/workflow/src/deno/fork-source.ts @@ -30,15 +30,15 @@ import { } from "./workspace/manifest.ts"; import { loadWorkspaceRoot } from "./workspace/root.ts"; -/** One retained journal row, exactly as the source stores it. */ -export interface RetainedJournalRow { +/** One saved journal row, exactly as the source stores it. */ +export interface SavedJournalRow { readonly eventId: string; readonly record: string; readonly workspaceRootId: string; } /** One DOFS manifest, as content the fork must hold for itself. */ -export interface RetainedManifest { +export interface SavedManifest { readonly hash: Uint8Array; readonly size: number; readonly encoded: Uint8Array; @@ -46,15 +46,15 @@ export interface RetainedManifest { } /** One DOFS blob and its bytes. */ -export interface RetainedBlob { +export interface SavedBlob { readonly hash: Uint8Array; readonly size: number; readonly lastSeen: number; readonly content: Uint8Array; } -/** One retained Repository checkout, as the fork inherits it. */ -export interface RetainedRepository { +/** One saved Repository checkout, as the fork inherits it. */ +export interface SavedRepository { readonly name: string; readonly locator: string; readonly locatorFingerprint: string; @@ -65,8 +65,8 @@ export interface RetainedRepository { readonly checkoutPath: string; } -/** One retained Worktree checkout, as the fork inherits it. */ -export interface RetainedWorktree { +/** One saved Worktree checkout, as the fork inherits it. */ +export interface SavedWorktree { readonly repositoryName: string; readonly name: string; readonly requestedBranch: string; @@ -85,12 +85,12 @@ export interface ForkSourceSnapshot { /** The root the source's root import was written against, when the prefix holds one. */ readonly rootImportWorkspaceRootId: string; /** The prefix without the two records the fork writes for itself. */ - readonly inherited: readonly RetainedJournalRow[]; + readonly inherited: readonly SavedJournalRow[]; readonly roots: readonly StoredWorkspaceRoot[]; - readonly manifests: readonly RetainedManifest[]; - readonly blobs: readonly RetainedBlob[]; - readonly repositories: readonly RetainedRepository[]; - readonly worktrees: readonly RetainedWorktree[]; + readonly manifests: readonly SavedManifest[]; + readonly blobs: readonly SavedBlob[]; + readonly repositories: readonly SavedRepository[]; + readonly worktrees: readonly SavedWorktree[]; } /** @@ -107,11 +107,11 @@ export function readForkSource( sourceRunId: string, checkpointEventId: string, ): ForkSourceSnapshot { - const rows = readRetainedRows(database); + const rows = readSavedRows(database); const at = rows.findIndex((row) => row.eventId === checkpointEventId); if (at === -1) { throw new WorkflowRequestError( - `no retained event of workflow run ${JSON.stringify(sourceRunId)} is ` + + `no saved event of workflow run ${JSON.stringify(sourceRunId)} is ` + `${JSON.stringify(checkpointEventId)}.`, ); } @@ -131,7 +131,7 @@ export function readForkSource( const checkpoint = prefix[at]; if (checkpoint === undefined) { - throw new WorkflowRequestError("the selected checkpoint is not a retained event"); + throw new WorkflowRequestError("the selected checkpoint is not a saved event"); } const rootIds = new Set(prefix.map((row) => row.workspaceRootId)); @@ -162,7 +162,7 @@ export function readForkSource( * The directories the checkpoint's Workspace held. * * A checkout exists in the fork only when the root the fork starts from - * contains its tree. Selecting by the manifest rather than by the retained + * contains its tree. Selecting by the manifest rather than by the saved * metadata is what keeps a Repository the source created *after* the checkpoint * from arriving in a fork whose Workspace has no such directory. */ @@ -188,14 +188,14 @@ function directoryPaths( } /** - * Every retained journal row, in append order. + * Every saved journal row, in append order. * * A fork slices a prefix out of this; an export seals all of it. Both read the * rows the same way, because what makes a row well formed is not a property of * which of them is asking. */ -export function readRetainedRows(database: DatabaseSync): RetainedJournalRow[] { - const rows: RetainedJournalRow[] = []; +export function readSavedRows(database: DatabaseSync): SavedJournalRow[] { + const rows: SavedJournalRow[] = []; for (const row of reading( database, "SELECT event_id, record, workspace_root_id FROM journal_events ORDER BY sequence ASC", @@ -226,15 +226,15 @@ export function readRetainedRows(database: DatabaseSync): RetainedJournalRow[] { return rows; } -function isRunRecord(row: RetainedJournalRow): boolean { +function isRunRecord(row: SavedJournalRow): boolean { return isRunRecordEvent(parsed(row)); } -function isRootImport(row: RetainedJournalRow): boolean { +function isRootImport(row: SavedJournalRow): boolean { return isRootImportEvent(parsed(row)); } -function parsed(row: RetainedJournalRow): DurableEvent { +function parsed(row: SavedJournalRow): DurableEvent { const event = parseDurableEvent(row.record); if (!event.ok) { throw new WorkflowRecordMalformedError("journal_events.record", event.error.message); @@ -242,7 +242,7 @@ function parsed(row: RetainedJournalRow): DurableEvent { return event.value; } -function readManifest(database: DatabaseSync, hash: string, path: string): RetainedManifest { +function readManifest(database: DatabaseSync, hash: string, path: string): SavedManifest { const row = reading( database, "SELECT hash, size, encoded, last_seen FROM vfs_manifests WHERE hex(hash) = upper(?)", @@ -260,7 +260,7 @@ function readManifest(database: DatabaseSync, hash: string, path: string): Retai }); } -function readBlob(database: DatabaseSync, hash: string, path: string): RetainedBlob { +function readBlob(database: DatabaseSync, hash: string, path: string): SavedBlob { const row = reading( database, `SELECT blob.hash AS hash, blob.size AS size, blob.last_seen AS last_seen, @@ -286,8 +286,8 @@ export function readRepositories( database: DatabaseSync, path: string, checkouts?: ReadonlySet, -): RetainedRepository[] { - const repositories: RetainedRepository[] = []; +): SavedRepository[] { + const repositories: SavedRepository[] = []; for (const row of reading(database, "SELECT * FROM workspace_repositories ORDER BY name").all()) { const checkoutPath = text(row["checkout_path"], "workspace_repositories.checkout_path"); // Absent selects every row. A fork takes only the checkouts its chosen root @@ -318,8 +318,8 @@ export function readWorktrees( database: DatabaseSync, path: string, checkouts?: ReadonlySet, -): RetainedWorktree[] { - const worktrees: RetainedWorktree[] = []; +): SavedWorktree[] { + const worktrees: SavedWorktree[] = []; for (const row of reading( database, "SELECT * FROM workspace_worktrees ORDER BY repository_name, name", diff --git a/packages/workflow/src/deno/journal.ts b/packages/workflow/src/deno/journal.ts index 06d34698e..6474841b0 100644 --- a/packages/workflow/src/deno/journal.ts +++ b/packages/workflow/src/deno/journal.ts @@ -24,7 +24,7 @@ import { import { WorkflowRecordMalformedError } from "../storage/errors.ts"; import { describe } from "../storage/members.ts"; -/** One retained event, the opaque id it keeps, and the root it was written against. */ +/** One saved event, the opaque id it keeps, and the root it was written against. */ export interface JournalEntry { readonly eventId: string; readonly event: DurableEvent; @@ -64,7 +64,7 @@ function currentWorkspaceRoot(database: DatabaseSync): string { return rootId; } -/** Every retained event, in the order it was appended. */ +/** Every saved event, in the order it was appended. */ export function readJournalEntries(database: DatabaseSync): JournalEntry[] { const entries: JournalEntry[] = []; diff --git a/packages/workflow/src/deno/lifecycle.ts b/packages/workflow/src/deno/lifecycle.ts index c6f764805..4df121d2d 100644 --- a/packages/workflow/src/deno/lifecycle.ts +++ b/packages/workflow/src/deno/lifecycle.ts @@ -96,7 +96,7 @@ import { workflowForkStaging, workflowRunPath } from "./path.ts"; import { authorizedRoot, checkRunId } from "./provider.ts"; import { reading, readTransaction } from "./reading.ts"; import type { DetachedXmdArtifact } from "./artifact/types.ts"; -import type { RetainedDefinitionSources } from "../lifecycle/source.ts"; +import type { SavedDefinitionSources } from "../lifecycle/source.ts"; import type { WorkflowDefinition } from "../storage/definition.ts"; import type { Json } from "@executablemd/durable-streams"; import { writeXmdArtifact } from "./artifact/mod.ts"; @@ -104,8 +104,8 @@ import { historyArtifact, inspectArtifact } from "./artifact-inspection.ts"; import { readExportFrontier, readRetrievalMetadata } from "./artifact-frontier.ts"; import { readDefinitionSourceRows, - type RetainedSourceRows, - verifyRetainedSources, + type SavedSourceRows, + verifySavedSources, } from "./definition-source.ts"; import type { WorkflowExportRequest, WorkflowExportResult } from "../lifecycle/export.ts"; import { readDocumentExecution, readRetrieval, readRunRecord } from "./rows.ts"; @@ -139,7 +139,7 @@ export type RecoveryPhase = * * The directory is host arrangement this installation just made, which is what * lets a test plant a real filesystem fault or start a real competing process - * at the exact moment that matters. It is not retained state, and nothing else + * at the exact moment that matters. It is not saved state, and nothing else * about the run travels with it: no lock, no transition, no database handle and * no value the run kept. */ @@ -451,18 +451,18 @@ function* readSource( } /** - * Remove one run's retained storage under its executor lock. + * Remove one run's saved storage under its executor lock. * * The lock decides whether there is anything to refuse: a live workflow executor holds * it, and a run somebody is running is not one to delete. Everything else may * be, including a `running` record whose workflow executor is gone — acquiring * the released lock is what proves it. * - * What goes is the run's database and the provider sessions it retained. The + * What goes is the run's database and the provider sessions it saved. The * lock file stays, empty: unlinking a file * this workflow executor still holds would let the next caller create and lock * a different file at the same path. An empty lock is host - * arrangement rather than retained run state, so it is not a category anybody + * arrangement rather than saved run state, so it is not a category anybody * is told about. */ function* remove( @@ -506,7 +506,7 @@ function* remove( yield* rm(path); // After the run's own storage, and under the same lock. A provider session - // is retained state this run owns, so deleting the run and leaving the + // is saved state this run owns, so deleting the run and leaving the // conversation it was having behind would be a deletion that reported more // than it did. Absent is not removed, and is not reported as such. const sessions = yield* removeProviderSessions(root, hold.runId); @@ -600,7 +600,7 @@ function* acquire( * * The read itself is the ordinary inspection path, which is what gives a * crashed source the existing recovery-copy discipline for free: a private copy - * is recovered and read, and the retained database is left untouched. + * is recovered and read, and the saved database is left untouched. */ function* exportRun( root: string, @@ -642,10 +642,10 @@ function* exportRun( // The run's own source store is the only place a closure comes from. Nothing // is fetched and no host capability is consulted: a run retains the bytes it // executes, so a run whose store is empty is corrupt rather than remote. - const closure: Result = + const closure: Result = selected.value.rows === undefined ? Err(new WorkflowDefinitionSourceMissingError()) - : yield* verifyRetainedSources(definition, selected.value.rows); + : yield* verifySavedSources(definition, selected.value.rows); if (!closure.ok) { return closure; } @@ -670,8 +670,8 @@ interface SelectedFrontier { readonly detached: Omit; readonly definition: WorkflowDefinition; readonly retrieval: Json | undefined; - /** The retained source store this run executes out of. */ - readonly rows?: RetainedSourceRows; + /** The saved source store this run executes out of. */ + readonly rows?: SavedSourceRows; } function* inspectRun( @@ -692,11 +692,11 @@ function* runHistory( runId, (database) => // Classified against the roots this database still holds, read in the - // same snapshot as the events. A root that was never retained and a root + // same snapshot as the events. A root that was never saved and a root // deleted since are the same fact to a fork: it cannot be given that // Workspace. projectHistory(readJournalEntries(database), { - retainedRoots: retainedRoots(database), + savedRoots: savedRoots(database), inherited: readEventProvenance(database), }), observe, @@ -726,7 +726,7 @@ function readEventProvenance( typeof sourceEventId !== "string" ) { throw new WorkflowRequestError( - "a retained inherited-event provenance row does not describe an inherited event.", + "a saved inherited-event provenance row does not describe an inherited event.", ); } provenance.set(eventId, Object.freeze({ sourceRunId, sourceEventId })); @@ -734,7 +734,7 @@ function readEventProvenance( return provenance; } -export function retainedRoots(database: DatabaseSync): ReadonlySet { +export function savedRoots(database: DatabaseSync): ReadonlySet { const roots = new Set(); for (const row of reading(database, "SELECT root_id FROM workspace_roots").all()) { const rootId = row["root_id"]; @@ -749,7 +749,7 @@ export function retainedRoots(database: DatabaseSync): ReadonlySet { * Read one run this caller named, refusing storage that holds a different one. * * Two questions, and the second is asked for every candidate rather than only - * here: `withSnapshot` refuses a database whose retained id does not name its + * here: `withSnapshot` refuses a database whose saved id does not name its * file at all, so what is left to ask is whether the run it holds is the run * that was asked for. */ @@ -792,7 +792,7 @@ function* listRuns( // run this root holds, and a shorter one silently answers a question the // caller did not ask. A candidate that is a perfectly good database of // *another* run is one of those: `withSnapshot` refuses it because its - // retained id does not name the file it was found in. + // saved id does not name the file it was found in. const read = yield* withSnapshot( path, (database, record) => snapshot(database, record, path), @@ -841,9 +841,9 @@ function* candidatePaths(root: string): Operation { * rather than only where a caller supplied an id to compare against. * * Two paths call this with two different files. The ordinary one reads the - * retained database, where `physical` and `source` are the same path. Recovered + * saved database, where `physical` and `source` are the same path. Recovered * inspection reads a private copy, where `physical` is that copy and `source` - * is still the retained database every diagnostic and every returned path is + * is still the saved database every diagnostic and every returned path is * about. Identity is not overridden between them: the copy is made under the * candidate's own filename, so the location check below asks the real file the * same question either way. @@ -920,9 +920,9 @@ function journalOf(database: string): string { } /** - * Inspect a crashed run without touching what it retained. + * Inspect a crashed run without touching what it saved. * - * The retained pair is copied under coordination and recovered nowhere near the + * The saved pair is copied under coordination and recovered nowhere near the * original: SQLite rolls the copy's journal back into the copy, and the answer * is read from that. What the run kept is exactly as its lost host left it when * this returns, still waiting for the write-capable owner whose job recovery diff --git a/packages/workflow/src/deno/path.ts b/packages/workflow/src/deno/path.ts index 7bd1dabf6..b04b068b4 100644 --- a/packages/workflow/src/deno/path.ts +++ b/packages/workflow/src/deno/path.ts @@ -31,7 +31,7 @@ export function workflowRunPath(root: string, runId: string): string { * A suffix in the same namespace as the lock, and a candidate pattern of * `.sqlite` exactly excludes it: nothing discovers it, nothing lists it, * and no run id resolves to it. That is what lets a fork be assembled in full — - * schema, run, inherited prefix, retained roots and a restored Workspace — and + * schema, run, inherited prefix, saved roots and a restored Workspace — and * replayed against its own filesystem before any storage the host would * recognize as the new run exists. * @@ -47,7 +47,7 @@ export function workflowForkStaging(root: string, runId: string): string { * * A suffix rather than a subdirectory, so every path a run occupies is derived * from one hash and inspection's candidate pattern — `.sqlite` exactly — - * excludes it by construction. It is not retained run state: it is how one host + * excludes it by construction. It is not saved run state: it is how one host * arranges ownership, and it may remain, empty, after the run is deleted. */ export function workflowRunLock(root: string, runId: string): string { diff --git a/packages/workflow/src/deno/provider-sessions.ts b/packages/workflow/src/deno/provider-sessions.ts index a4153e904..76ceda086 100644 --- a/packages/workflow/src/deno/provider-sessions.ts +++ b/packages/workflow/src/deno/provider-sessions.ts @@ -1,7 +1,7 @@ /** * Where a run keeps the disposable arrangement its Agent sessions need. * - * Two things, and neither is retained state: the provider's own persistent + * Two things, and neither is saved state: the provider's own persistent * session store, and one empty working directory per session. What a * continuation is decided from is not here at all — it is a row in the run's own * database (`workspace/agent-sessions.ts`), because a mapping and the run it @@ -85,7 +85,7 @@ export function* useEmptyDirectory(path: string): Operation { * filesystem rather than merely asserted: the directories below appear when a * session is placed and a record is written, and not before. * - * What it does own is the end. The retained half — the mapping records and the + * What it does own is the end. The saved half — the mapping records and the * provider's own session store — outlives the attachment, because that is what a * continuation reads. The working directories do not, and their removal is * registered before anything is installed over this, so it runs after provider diff --git a/packages/workflow/src/deno/provider.ts b/packages/workflow/src/deno/provider.ts index 494f2e20c..b86661fee 100644 --- a/packages/workflow/src/deno/provider.ts +++ b/packages/workflow/src/deno/provider.ts @@ -355,7 +355,7 @@ const REQUEST_MEMBERS = ["runId", "definition", "sourceSnapshot", "props"]; * The run id a caller named, whichever operation they called. * * `create` and `lookup` address the same runs by the same rule, so they check - * it in the same place — and it is the same rule a retained `run_id` is read + * it in the same place — and it is the same rule a saved `run_id` is read * back through, so an id no request could address cannot be stored and then * answered for. Nothing here assumes the argument is a string: a host built * without types, or one that read the id out of a file, can hand over anything diff --git a/packages/workflow/src/deno/rows.ts b/packages/workflow/src/deno/rows.ts index 272c7fba4..d769de740 100644 --- a/packages/workflow/src/deno/rows.ts +++ b/packages/workflow/src/deno/rows.ts @@ -8,7 +8,7 @@ * looked right. * * A failure names the column and never the value. Props and journal payloads - * are retained history, and a row that does not parse is not a reason to print + * are saved history, and a row that does not parse is not a reason to print * what it held. */ @@ -190,7 +190,7 @@ export function readDefinition(row: Row, table: string): WorkflowDefinition { } /** - * The retained run id, held to the rule a caller's id is held to. + * The saved run id, held to the rule a caller's id is held to. * * Storage that retains an id no request could ever name is storage nothing can * address, and reading it back as though it addressed something is how a run diff --git a/packages/workflow/src/deno/savepoints.ts b/packages/workflow/src/deno/savepoints.ts index 7ba192457..f955f1bdb 100644 --- a/packages/workflow/src/deno/savepoints.ts +++ b/packages/workflow/src/deno/savepoints.ts @@ -19,7 +19,7 @@ export interface SavepointManager { * Run `body` and discard everything it wrote, however it ends. * * For work whose whole product is outside this database — reading the - * Workspace as a retained root held it, by materializing that root and + * Workspace as a saved root held it, by materializing that root and * exporting from it. The materialization is a means, not a result, so there * is no path on which it commits: success rolls back exactly as failure and * cancellation do. diff --git a/packages/workflow/src/deno/schema.ts b/packages/workflow/src/deno/schema.ts index ed6cd923c..30a692448 100644 --- a/packages/workflow/src/deno/schema.ts +++ b/packages/workflow/src/deno/schema.ts @@ -77,7 +77,7 @@ function coherentStopReason(): string { * Kept as separate definitions so verification can compare what a file holds * with what this build writes, rather than settling for the table's name. * - * The complete version-1 shape includes the pinned DOFS objects, retained + * The complete version-1 shape includes the pinned DOFS objects, saved * Workspace roots, journal and metadata. Dependency order is explicit: DOFS * content precedes root references, and roots precede the journal rows that * name them. @@ -828,8 +828,8 @@ export function checkIntegrity(database: DatabaseSync, path: string): void { /** * Ask SQLite whether its references still point at anything. * - * A retained reference names an object only while that object exists; a row - * pointing at nothing is damage rather than a partial retained state. + * A saved reference names an object only while that object exists; a row + * pointing at nothing is damage rather than a partial saved state. */ function checkForeignKeys(database: DatabaseSync, path: string): void { if (query(database, "PRAGMA foreign_key_check", path).length > 0) { diff --git a/packages/workflow/src/deno/suspension.ts b/packages/workflow/src/deno/suspension.ts index a8b96b443..6ec8fadd0 100644 --- a/packages/workflow/src/deno/suspension.ts +++ b/packages/workflow/src/deno/suspension.ts @@ -29,7 +29,7 @@ * binary installed. That name is composition: middleware may refuse it for its * descendants, and nothing it returns is an answer. * - * What authorizes entry is the retained request at the caller's exact current + * What authorizes entry is the saved request at the caller's exact current * durable position. Another durable operation can reproduce that request and * arrive there — replay identity is a type and a name, both public — and when it * does, it is standing at the same validated wait rather than at one of its own. @@ -69,7 +69,7 @@ import { } from "../suspension/answer.ts"; import type { WorkflowRunDatabase } from "../storage/api.ts"; import { WorkflowRequestError, WorkflowTransactionError } from "../storage/errors.ts"; -import { consumeRetainedAnswer, readRetainedAnswer } from "./answers.ts"; +import { consumeSavedAnswer, readSavedAnswer } from "./answers.ts"; import type { WorkflowRunConnections } from "./connections.ts"; import { hostConnections } from "./host-connections.ts"; import { withEnlistedJournalRoute } from "./journal-route.ts"; @@ -83,7 +83,7 @@ export interface SuspensionNotice { export interface SuspensionControllerOptions { /** - * The run whose retained request at the caller's position admits a wait. + * The run whose saved request at the caller's position admits a wait. * * Read here rather than trusted from the caller: the identifier presented has * to be the one this run derives for the position immediately behind the @@ -114,7 +114,7 @@ export interface SuspensionController { * Whether this execution is, right now, at the wait it says it is. * * What admits a wait is the *current* execution reaching its own request, not the - * existence of a matching row. Retained history alone cannot decide this: on a + * existence of a matching row. Saved history alone cannot decide this: on a * resume the request from the previous execution is already in the journal, so * a caller that ran before replay reached it could present its identifier and be * believed. What separates the real wait from that is where the execution is. @@ -167,21 +167,21 @@ function* atOwnRequest( if (found === undefined || found.type !== SUSPENSION_REQUEST || found.name !== suspension) { return NOT_AT_A_WAIT; } - // Parsed, not merely read. A retained description is journal data, and this + // Parsed, not merely read. A saved description is journal data, and this // one is reached through a public durable operation any document can publish, // so what it holds is a claim about a request rather than a request. Comparing // raw fields would let a row that could never have come from `suspendFor()` — // a `responseSchema` that is an array, say — admit a wait whose schema nothing // could later validate an answer against. - let retained: WorkflowSuspensionRequest; + let saved: WorkflowSuspensionRequest; try { - retained = parseSuspensionRequest({ + saved = parseSuspensionRequest({ request: found.request, responseSchema: found.responseSchema, }); } catch (error) { return ( - "the request retained at this position is not one a durable wait can be entered " + + "the request saved at this position is not one a durable wait can be entered " + `for: ${error instanceof Error ? error.message : String(error)}` ); } @@ -192,8 +192,8 @@ function* atOwnRequest( responseSchema: request.responseSchema, }) === canonicalFingerprint({ - request: retained.request, - responseSchema: retained.responseSchema, + request: saved.request, + responseSchema: saved.responseSchema, }); return same ? undefined : NOT_AT_A_WAIT; } @@ -225,10 +225,10 @@ export function createSuspensionController( own(operation: Operation): Operation { return scoped(function* () { - // The run's retained delivery state is this host's, so the answer + // The run's saved delivery state is this host's, so the answer // provider is installed only where this host's registry is. A // controller running without one enters waits and never claims: an - // execution with no way to reach retained state has no answers. + // execution with no way to reach saved state has no answers. const connections = yield* hostConnections(); if (connections !== undefined) { yield* useSuspensionAnswerProvider(answerProvider(options.database, connections)); @@ -284,7 +284,7 @@ export function createSuspensionController( } /** - * The owner of one run's retained delivery state. + * The owner of one run's saved delivery state. * * Two facts have to move together and only this host can move them: the answer * stops being pending, and the journal gains the event that says the wait ended. @@ -312,7 +312,7 @@ function answerProvider( const pending = yield* scoped(function* () { yield* connection.lock.hold(); return readTransaction(connection.database, () => - readRetainedAnswer(connection.database, publication.suspensionId), + readSavedAnswer(connection.database, publication.suspensionId), ); }); if (pending === undefined || pending.state !== "pending") { @@ -335,16 +335,16 @@ function answerProvider( // have been claimed by another execution, and the publication that // commits with the consumption has to be of the value that consumption // took. - const retained = readRetainedAnswer(writable.database, publication.suspensionId); - if (retained === undefined || retained.state !== "pending") { + const saved = readSavedAnswer(writable.database, publication.suspensionId); + if (saved === undefined || saved.state !== "pending") { throw new WorkflowRequestError( - `the answer retained for ${publication.suspensionId} was consumed by another ` + + `the answer saved for ${publication.suspensionId} was consumed by another ` + "execution before this one could publish it.", ); } - if (retained.requestFingerprint !== suspensionRequestFingerprint(publication.request)) { + if (saved.requestFingerprint !== suspensionRequestFingerprint(publication.request)) { throw new WorkflowRequestError( - `the answer retained for ${publication.suspensionId} was delivered against a ` + + `the answer saved for ${publication.suspensionId} was delivered against a ` + "different request, so it is not an answer to the wait this execution reached.", ); } @@ -353,22 +353,18 @@ function answerProvider( database, transaction, token, - publication.publish(retained.answer), + publication.publish(saved.answer), ); if ( - !consumeRetainedAnswer( - writable.database, - publication.suspensionId, - new Date().toISOString(), - ) + !consumeSavedAnswer(writable.database, publication.suspensionId, new Date().toISOString()) ) { throw new WorkflowRequestError( - `the answer retained for ${publication.suspensionId} could not be consumed, so its ` + + `the answer saved for ${publication.suspensionId} could not be consumed, so its ` + "publication is discarded with this transaction.", ); } - return retained.answer; + return saved.answer; }); if (!claimed.ok) { throw claimed.error; diff --git a/packages/workflow/src/deno/transitions.ts b/packages/workflow/src/deno/transitions.ts index b5b2fcf2f..9673f44bc 100644 --- a/packages/workflow/src/deno/transitions.ts +++ b/packages/workflow/src/deno/transitions.ts @@ -34,7 +34,7 @@ import { type WorkflowForkRequest, type WorkflowRunCreation, } from "../lifecycle/execution.ts"; -import type { RetainedDefinitionSources } from "../lifecycle/source.ts"; +import type { SavedDefinitionSources } from "../lifecycle/source.ts"; import type { WorkflowRunDatabase } from "../storage/api.ts"; import { conflictingFields, type WorkflowRunComparison } from "../storage/compatibility.ts"; import { definitionToJson } from "../storage/definition.ts"; @@ -67,8 +67,8 @@ import { readJournalEntries } from "./journal.ts"; import { readRetrievalMetadata } from "./artifact-frontier.ts"; import { readDefinitionSourceRows, - type RetainedSourceRows, - verifyRetainedSources, + type SavedSourceRows, + verifySavedSources, writeDefinitionSources, } from "./definition-source.ts"; import type { ForkSourceSnapshot } from "./fork-source.ts"; @@ -134,7 +134,7 @@ export function* beginExecution( const connection = yield* connections.at(path); // Everything the source has to prove, proved first. This caller holds the - // executor lock, and nothing here writes — so a run whose retained bytes are + // executor lock, and nothing here writes — so a run whose saved bytes are // missing, damaged or unobtainable is refused with its lifecycle and its // journal exactly as they were, rather than after a recovery it then has to // leave behind. @@ -195,7 +195,7 @@ interface AuthenticatedSource { * path — an existing run of either version, and a Git run being created from * a descriptor the reader can already be asked about — proves it here. */ - readonly proved?: RetainedDefinitionSources; + readonly proved?: SavedDefinitionSources; /** The creation's snapshot, copied and checked against its descriptor. */ readonly owned?: readonly SourceBundleSnapshotEntry[]; } @@ -207,7 +207,7 @@ interface AuthenticatedSource { * after a write. A version-2 creation's snapshot is copied and held to its own * descriptor, so a caller that keeps mutating its arrays changes nothing and a * snapshot that does not describe the descriptor never reaches storage. An - * existing run's retained source is re-derived from what it holds, or — for a + * existing run's saved source is re-derived from what it holds, or — for a * version-1 run — fetched through the host's legacy reader and validated * against the descriptor the run retains. */ @@ -235,7 +235,7 @@ function* authenticateBeforeBegin( } // A run that is already there is held to what it retains. A run that is being - // created has nothing retained yet — its bytes are the snapshot verified + // created has nothing saved yet — its bytes are the snapshot verified // above, and the closure it executes is read back out of the store once the // transaction has committed them. if (stored.value === undefined) { @@ -253,7 +253,7 @@ function* authenticateBeforeBegin( interface StoredSource { readonly record: WorkflowRunRecord; readonly retrieval: Json | undefined; - readonly rows?: RetainedSourceRows; + readonly rows?: SavedSourceRows; } /** @@ -299,12 +299,12 @@ function* readStoredSource( * admitted without knowing what it executes: every version-1 lifecycle * admission is gated before it writes. */ -function* authenticate(stored: StoredSource): Operation> { +function* authenticate(stored: StoredSource): Operation> { const { definition } = stored.record; if (stored.rows === undefined) { return Err(new WorkflowDefinitionSourceMissingError()); } - return yield* verifyRetainedSources(definition, stored.rows); + return yield* verifySavedSources(definition, stored.rows); } /** @@ -319,15 +319,15 @@ function* settleSources( connection: RunConnection, path: string, record: WorkflowRunRecord, - proved: RetainedDefinitionSources | undefined, -): Operation> { + proved: SavedDefinitionSources | undefined, +): Operation> { if (proved !== undefined) { return Ok(proved); } // The one case left: a run this call created. Its content existed nowhere // until the transaction committed, so it is read back out of the store and // verified again — which is what makes the closure a caller imports the - // retained bytes rather than the buffers it supplied. + // saved bytes rather than the buffers it supplied. const stored = yield* readStoredSource(connection, path); if (!stored.ok) { return stored; @@ -765,7 +765,7 @@ function forkConflicts( /** * Finish this execution and publish what the run became, together. * - * A status line says what was retained, so the record that says the execution + * A status line says what was saved, so the record that says the execution * ended and the state that says what it ended as commit at once or not at all. */ export function* settleExecution( @@ -959,7 +959,7 @@ function admissionRefusal( } if (status === "cancelled") { return new WorkflowRequestError( - "workflow run cancelled: a cancelled run reports its retained state and is not advanced.", + "workflow run cancelled: a cancelled run reports its saved state and is not advanced.", ); } return undefined; @@ -972,7 +972,7 @@ function terminal(status: WorkflowRunStatus): boolean { /** * Close what the previous workflow executor left, on the evidence the run itself holds. * - * Precedence is the architecture's. A retained root Close proves the canonical + * Precedence is the architecture's. A saved root Close proves the canonical * outcome won before anything else could; failing that, a cancellation * addressed to the exact generation that died settles the execution it was * addressed to; failing both, the execution was interrupted. @@ -1011,7 +1011,7 @@ interface Closing { /** * What the previous workflow executor's execution became, on the evidence the run holds. * - * A retained root Close proves the canonical outcome won before anything could + * A saved root Close proves the canonical outcome won before anything could * interrupt it, so it is restored. Failing that, the execution was interrupted: * the workflow executor went away without recording an outcome, and that is what happened. */ @@ -1037,7 +1037,7 @@ const interrupted = { kind: "host", code: "executor-interrupted" } as const; * * A root Close is what proves the document itself finished. Its result decides * the run's terminal status, and its own event identity is the reason — the - * journal already filtered it, so nothing new is retained to say why. + * journal already filtered it, so nothing new is saved to say why. */ function rootOutcome( database: DatabaseSync, @@ -1197,7 +1197,7 @@ function refusal(error: unknown, path: string): Result { * Make one run terminal under the executor lock without starting anything. * * There is no live execution to halt here: acquiring the lock is what proved - * that. What is left is retained state, and the rules are the architecture's — + * that. What is left is saved state, and the rules are the architecture's — * a root Close means the canonical outcome already won and cancellation is * refused; an unfinished execution left by a workflow executor that went away is finished * as cancelled; a run with nothing running becomes cancelled directly; a run diff --git a/packages/workflow/src/deno/workspace/agent-checkpoints.ts b/packages/workflow/src/deno/workspace/agent-checkpoints.ts index 56190d4c1..935dafa5c 100644 --- a/packages/workflow/src/deno/workspace/agent-checkpoints.ts +++ b/packages/workflow/src/deno/workspace/agent-checkpoints.ts @@ -2,7 +2,7 @@ * Which provider turn each of this run's Prompts was * (specs/workflow-workspace-spec.md §8.6). * - * A retained Agent session says which conversation this run is having. A + * A saved Agent session says which conversation this run is having. A * checkpoint says where in that conversation one Prompt landed — the identity * the provider gave the turn it had just completed. Together they are what lets * something later continue from an exact point rather than from "the latest diff --git a/packages/workflow/src/deno/workspace/agent-sessions.ts b/packages/workflow/src/deno/workspace/agent-sessions.ts index 8f73c9a39..d8ceb3614 100644 --- a/packages/workflow/src/deno/workspace/agent-sessions.ts +++ b/packages/workflow/src/deno/workspace/agent-sessions.ts @@ -6,7 +6,7 @@ * reattaching the conversation it was having rather than replaying transcript * text into a new one. What makes that decidable is one row in the run's own * database, written in the run's own transaction — beside the journal it - * belongs to, for the same reason retained Repository identity lives there: a + * belongs to, for the same reason saved Repository identity lives there: a * conversation and the record naming it are one fact, and a record that could * commit without the run committing would describe a session this run never had. * @@ -40,7 +40,7 @@ import type { DatabaseSync } from "node:sqlite"; import { createHash } from "node:crypto"; -/** A retained Agent session this host will not continue under. */ +/** A saved Agent session this host will not continue under. */ export class WorkflowAgentSessionError extends Error { override name = "WorkflowAgentSessionError"; } @@ -67,7 +67,7 @@ export interface AgentSessionIdentity { readonly sessionIdentity: string; } -/** One retained mapping, as the run's database holds it. */ +/** One saved mapping, as the run's database holds it. */ export interface AgentSessionRecord extends AgentSessionIdentity { readonly sessionKey: string; /** The session policy in force when the provider created this session. */ @@ -81,7 +81,7 @@ function digest(value: string): string { } /** - * The key one logical session is retained under, within this run. + * The key one logical session is saved under, within this run. * * The engine-derived Session expansion identity and nothing else. The provider * and the resolved agent command are compatibility attributes stored beside it: @@ -106,7 +106,7 @@ const INSERT = `INSERT INTO agent_sessions (session_key, provider, agent_command session_identity, policy, assertion_kind, assertion_value, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`; -/** Every retained mapping this run holds. Reading is not a transaction. */ +/** Every saved mapping this run holds. Reading is not a transaction. */ export interface AgentSessions { read(sessionKey: string): AgentSessionRecord | undefined; commit(record: AgentSessionRecord): void; @@ -207,7 +207,7 @@ export type AgentSessionResolution = * name. */ export function resolveAgentSession( - retained: AgentSessionRecord | undefined, + saved: AgentSessionRecord | undefined, policy: string, asserted: readonly ProviderAssertion[], identity: AgentSessionIdentity, @@ -222,7 +222,7 @@ export function resolveAgentSession( } const current = asserted[0]; - if (retained === undefined) { + if (saved === undefined) { if (current === undefined) { // Neither side holds anything: nothing was ever established here. return { kind: "create", sessionKey }; @@ -243,10 +243,10 @@ export function resolveAgentSession( } if ( - retained.provider !== identity.provider || - retained.agentCommand !== identity.agentCommand || - retained.sessionIdentity !== identity.sessionIdentity || - retained.policy !== policy + saved.provider !== identity.provider || + saved.agentCommand !== identity.agentCommand || + saved.sessionIdentity !== identity.sessionIdentity || + saved.policy !== policy ) { throw new WorkflowAgentSessionError( "this run's Agent session was established under a different provider, agent or session " + @@ -256,17 +256,17 @@ export function resolveAgentSession( } if (current === undefined) { throw new WorkflowAgentSessionError( - "the provider asserts no durable identity for the Agent session this run retained, and " + + "the provider asserts no durable identity for the Agent session this run saved, and " + "this host does not reconstruct a conversation by replaying it into a new session. " + "Start a new run rather than continuing this one.", ); } - if (current.kind !== retained.assertion.kind || current.value !== retained.assertion.value) { + if (current.kind !== saved.assertion.kind || current.value !== saved.assertion.value) { throw new WorkflowAgentSessionError( "the provider asserts a different durable identity than the Agent session this run " + - "retained, so it did not resume the conversation this run was having. This host does " + + "saved, so it did not resume the conversation this run was having. This host does " + "not continue under a replacement session.", ); } - return { kind: "reattach", record: retained }; + return { kind: "reattach", record: saved }; } diff --git a/packages/workflow/src/deno/workspace/files.ts b/packages/workflow/src/deno/workspace/files.ts index 7e4377fa6..25ecdf0ff 100644 --- a/packages/workflow/src/deno/workspace/files.ts +++ b/packages/workflow/src/deno/workspace/files.ts @@ -115,7 +115,7 @@ function lexicalReason(error: Error): FilesReason { * * A JSON value, because it is what the journal holds and what a replay hands * back. A refusal is carried as a phase and a reason rather than as a - * serialized error, so nothing a filesystem said is retained and a restored + * serialized error, so nothing a filesystem said is saved and a restored * refusal is rebuilt from the same vocabulary a live one is. */ type FileEffectOutcome = @@ -795,7 +795,7 @@ export function workflowFilesHandler(database: WorkflowRunDatabase): WorkflowFil * Denied rather than emulated inside the Workspace: `` exists so a * document can hand a path to a tool the caller already has, and a logical * path is not one. Falling through to the host would give a run exactly the - * unretained, uncontained filesystem the boundary exists to keep it out of. + * unsaved, uncontained filesystem the boundary exists to keep it out of. */ // deno-lint-ignore require-yield *temporaryDirectory(): Operation> { diff --git a/packages/workflow/src/deno/workspace/guard.ts b/packages/workflow/src/deno/workspace/guard.ts index b42786b4a..bea3dfc04 100644 --- a/packages/workflow/src/deno/workspace/guard.ts +++ b/packages/workflow/src/deno/workspace/guard.ts @@ -1,7 +1,7 @@ /** * Revoke callback-owned Workspace capabilities before later publication work. * The transaction remains open after a mutation or inspection callback returns; - * retained filesystem, storage and savepoint views must not reach that live window. + * saved filesystem, storage and savepoint views must not reach that live window. * One gate revokes every projection of the callback's argument together. * * Operation-returning members check both when called and when yielded, preventing diff --git a/packages/workflow/src/deno/workspace/host.ts b/packages/workflow/src/deno/workspace/host.ts index 8854e3ebe..ad0283340 100644 --- a/packages/workflow/src/deno/workspace/host.ts +++ b/packages/workflow/src/deno/workspace/host.ts @@ -34,7 +34,7 @@ * `withWorkflowWorkspace()` is therefore the whole of what a host may install. * The pieces are not published separately: the Files provider alone would * resolve a document's paths against whatever working directory the host adapter - * answers with, and a host path resolved that way is retained in the durable + * answers with, and a host path resolved that way is saved in the durable * effects a run replays from. */ @@ -79,7 +79,7 @@ export interface WorkflowWorkspaceOptions { * The further providers this host attaches to the run's Workspace. * * Where a feature outside this package installs what its own vocabulary - * needs — the Repository and Git providers, the retained lifecycles for a + * needs — the Repository and Git providers, the saved lifecycles for a * service-reaching effect, its component registrations. They are installed * here, in authored order, after the Files provider and the logical working * directory and before ``, because that is the position the run's own diff --git a/packages/workflow/src/deno/workspace/inspect.ts b/packages/workflow/src/deno/workspace/inspect.ts index 0dfa2e372..da3384c0d 100644 --- a/packages/workflow/src/deno/workspace/inspect.ts +++ b/packages/workflow/src/deno/workspace/inspect.ts @@ -1,5 +1,5 @@ /** - * Read authenticated retained Workspace state without a durable effect, journal + * Read authenticated saved Workspace state without a durable effect, journal * entry or root publication. The transaction ends with the callback, before later * host work such as a Git subprocess. * @@ -9,7 +9,7 @@ * start. * * An optional historical root is materialized in a rollback-only savepoint. Every - * exit, including failure and cancellation, restores the current root, retained + * exit, including failure and cancellation, restores the current root, saved * roots, rows and journal; no other observer sees the historical root selected. */ @@ -39,7 +39,7 @@ export interface WorkflowWorkspaceSnapshot { /** Which root an inspection reads. */ export interface WorkflowWorkspaceReadOptions { /** - * The retained root to read, or the current one when omitted. + * The saved root to read, or the current one when omitted. * * Named rather than searched for: a root this run does not retain is refused * by the same restoration that a resumed run is held to, and the root the @@ -75,7 +75,7 @@ export function* readWorkflowWorkspace( if (wanted === undefined || wanted === (yield* workspace.currentRoot())) { return yield* read(); } - return yield* workspace.readRetainedRoot(wanted, read); + return yield* workspace.readSavedRoot(wanted, read); } finally { gate.revoke(); } diff --git a/packages/workflow/src/deno/workspace/manifest.ts b/packages/workflow/src/deno/workspace/manifest.ts index 47c0b3893..0c99aa76e 100644 --- a/packages/workflow/src/deno/workspace/manifest.ts +++ b/packages/workflow/src/deno/workspace/manifest.ts @@ -119,15 +119,15 @@ export function parseWorkspaceManifest( try { offered = JSON.parse(manifest); } catch { - reject("one of its retained Workspace roots is not JSON"); + reject("one of its saved Workspace roots is not JSON"); } const parsed = rootManifestSchema.safeParse(offered); if (!parsed.success) { - reject("one of its retained Workspace roots has an invalid manifest"); + reject("one of its saved Workspace roots has an invalid manifest"); } validateWorkspaceEntries(parsed.data.entries, databasePath, reject); if (JSON.stringify(parsed.data) !== manifest) { - reject("one of its retained Workspace roots is not canonically encoded"); + reject("one of its saved Workspace roots is not canonically encoded"); } return parsed.data; } diff --git a/packages/workflow/src/deno/workspace/private.ts b/packages/workflow/src/deno/workspace/private.ts index 67a2dac08..33ec64ed2 100644 --- a/packages/workflow/src/deno/workspace/private.ts +++ b/packages/workflow/src/deno/workspace/private.ts @@ -56,7 +56,7 @@ export interface PrivateWorkspaceTransaction { */ readonly agentSessions: AgentSessions; /** - * Which provider turn each retained Prompt was, inside this same + * Which provider turn each saved Prompt was, inside this same * transaction. * * Beside the session mapping for the same reason that sits beside the @@ -86,20 +86,20 @@ export interface PrivateWorkspaceTransaction { publish(rootId: string): Operation; restore(rootId: string, options?: RestoreWorkspaceRootOptions): Operation; /** - * Read the Workspace as one retained root held it, and leave it as it was. + * Read the Workspace as one saved root held it, and leave it as it was. * * Reading history rather than returning to it. A replayed effect sometimes * has to name what it asked for at the position it asked from, and the * Workspace has moved on since — so the root is materialized, `body` reads * it, and everything that materialization wrote is discarded. The current - * root, the retained roots, the metadata and the journal are what they were, + * root, the saved roots, the metadata and the journal are what they were, * on every path out: nothing here publishes, and there is no ordering in * which a caller observes the Workspace selected at the historical root. * * What `body` carries out of it is its own. An export writes host files * outside this database, and those are the point. */ - readRetainedRoot(rootId: string, body: () => Operation): Operation; + readSavedRoot(rootId: string, body: () => Operation): Operation; } interface PrivateWorkspaceApi { @@ -236,7 +236,7 @@ export function usePrivateWorkspace( return restoreWorkspaceRoot(connection, active, rootId, options); }, - *readRetainedRoot(rootId: string, body: () => Operation): Operation { + *readSavedRoot(rootId: string, body: () => Operation): Operation { authorize(); return yield* connection.savepoints.rollbackOnly( active, @@ -289,7 +289,7 @@ export function* transactWorkspaceRoots( * * The narrow half of the transaction above, and the only half a host installing * an Agent profile needs. The broad one hands out the authoritative filesystem, - * retained Repository identity, root capture, publication and restoration; a + * saved Repository identity, root capture, publication and restoration; a * caller that only has to read and commit a session mapping should not be * holding any of that, and this is what it holds instead. */ diff --git a/packages/workflow/src/deno/workspace/restore.ts b/packages/workflow/src/deno/workspace/restore.ts index 375994ef9..e418e1393 100644 --- a/packages/workflow/src/deno/workspace/restore.ts +++ b/packages/workflow/src/deno/workspace/restore.ts @@ -46,7 +46,7 @@ export function restoreWorkspaceRoot( !equalStrings(restored.manifestHashes, selected.manifestHashes) || !equalStrings(restored.blobHashes, selected.blobHashes) ) { - corrupt(path, "a retained Workspace root did not materialize to its own identity"); + corrupt(path, "a saved Workspace root did not materialize to its own identity"); } if (options.publish === true) { setCurrentWorkspaceRoot(database, selected.rootId, path); @@ -62,7 +62,7 @@ function rebuild(database: DatabaseSync, root: StoredWorkspaceRoot, databasePath const parsed = parseWorkspaceManifest(root.manifest, databasePath); const rootEntry = parsed.entries[0]; if (rootEntry === undefined || rootEntry.kind !== "directory" || rootEntry.path !== "/") { - corrupt(databasePath, "a retained Workspace root has no root directory"); + corrupt(databasePath, "a saved Workspace root has no root directory"); } database.exec("DELETE FROM vfs_dirents"); @@ -86,7 +86,7 @@ function rebuild(database: DatabaseSync, root: StoredWorkspaceRoot, databasePath const parent = parentPath(entry.path); const parentInode = inodes.get(parent); if (parentInode === undefined) { - corrupt(databasePath, "a retained Workspace root names a child without a parent"); + corrupt(databasePath, "a saved Workspace root names a child without a parent"); } const name = entry.path.slice(parent === "/" ? 1 : parent.length + 1); const inode = materializeNode(database, entry, revision, hardlinks, databasePath); @@ -133,7 +133,7 @@ function materializeNode( } else { const manifest = readDofsManifest(database, entry.manifest, databasePath); if (manifest.size !== entry.size) { - corrupt(databasePath, "a retained file size differs from its DOFS manifest"); + corrupt(databasePath, "a saved file size differs from its DOFS manifest"); } const result = database .prepare( diff --git a/packages/workflow/src/deno/workspace/root.ts b/packages/workflow/src/deno/workspace/root.ts index 55db2b482..a2d5c8cd9 100644 --- a/packages/workflow/src/deno/workspace/root.ts +++ b/packages/workflow/src/deno/workspace/root.ts @@ -273,7 +273,7 @@ export function retainWorkspaceRoot( WORKSPACE_ROOT_FORMAT || existing["manifest"] !== root.manifest ) { - corrupt(databasePath, "a retained Workspace root identity has different stored bytes"); + corrupt(databasePath, "a saved Workspace root identity has different stored bytes"); } requireReferenceSet( @@ -307,7 +307,7 @@ export function loadWorkspaceRoot( "SELECT root_id, format_version, manifest FROM workspace_roots WHERE root_id = ?", ).get(rootId); if (row === undefined) { - corrupt(databasePath, "the selected Workspace root is not retained"); + corrupt(databasePath, "the selected Workspace root is not saved"); } return parseStoredRoot(database, row, databasePath); } @@ -331,7 +331,7 @@ export function setCurrentWorkspaceRoot( * The set a generated-XMD admission states its as-of-admission basis over. A * continuation asks for that basis by membership, so ordering no longer decides * whether a resumed run holds its grant — it stays deterministic so the - * retained record reads the same however SQLite would have ordered the rows, + * saved record reads the same however SQLite would have ordered the rows, * and `root_id` is the only stable ordering this table has, stable across * processes because it is content. * @@ -339,7 +339,7 @@ export function setCurrentWorkspaceRoot( * that one answers a set membership question on a read-only connection, and this * one belongs to a live run. */ -export function retainedWorkspaceRoots(database: DatabaseSync): string[] { +export function savedWorkspaceRoots(database: DatabaseSync): string[] { const roots: string[] = []; for (const row of reading( database, @@ -373,16 +373,16 @@ export function verifyWorkspace( databasePath: string, ): void { validateDofsContentStore(database, databasePath); - const retained = new Map(); + const saved = new Map(); for (const row of reading( database, "SELECT root_id, format_version, manifest FROM workspace_roots ORDER BY root_id", ).all()) { const root = parseStoredRoot(database, row, databasePath); - if (retained.has(root.rootId)) { - corrupt(databasePath, "it contains a duplicate retained Workspace root"); + if (saved.has(root.rootId)) { + corrupt(databasePath, "it contains a duplicate saved Workspace root"); } - retained.set(root.rootId, root); + saved.set(root.rootId, root); requireReferenceSet( database, root.rootId, @@ -400,24 +400,24 @@ export function verifyWorkspace( databasePath, ); } - if (retained.size === 0) { - corrupt(databasePath, "it contains no retained Workspace root"); + if (saved.size === 0) { + corrupt(databasePath, "it contains no saved Workspace root"); } - const unretainedJournalRoots = reading( + const unsavedJournalRoots = reading( database, `SELECT COUNT(*) AS count FROM journal_events AS event LEFT JOIN workspace_roots AS root ON root.root_id = event.workspace_root_id WHERE root.root_id IS NULL`, ).get(); - if (integer(unretainedJournalRoots?.["count"], databasePath, "journal root count") !== 0) { - corrupt(databasePath, "a journal event names a Workspace root that is not retained"); + if (integer(unsavedJournalRoots?.["count"], databasePath, "journal root count") !== 0) { + corrupt(databasePath, "a journal event names a Workspace root that is not saved"); } const current = currentWorkspaceRoot(database, databasePath); - if (!retained.has(current)) { - corrupt(databasePath, "its current Workspace root is not retained"); + if (!saved.has(current)) { + corrupt(databasePath, "its current Workspace root is not saved"); } const live = snapshotWorkspace(database, dofs, databasePath, false); if (live.rootId !== current) { @@ -436,7 +436,7 @@ export function readDofsManifest( "SELECT hash, size, encoded, last_seen FROM vfs_manifests WHERE hash = ?", ).get(hashBytes); if (row === undefined) { - corrupt(databasePath, "a retained Workspace root names a missing DOFS manifest"); + corrupt(databasePath, "a saved Workspace root names a missing DOFS manifest"); } if (toHex(bytes(row["hash"], databasePath, "DOFS manifest hash")) !== hash) { corrupt(databasePath, "a DOFS manifest row carries the wrong identity"); @@ -501,12 +501,12 @@ function parseStoredRoot( format !== WORKSPACE_ROOT_FORMAT || typeof manifest !== "string" ) { - corrupt(databasePath, "one of its retained Workspace roots is malformed"); + corrupt(databasePath, "one of its saved Workspace roots is malformed"); } const parsed = parseWorkspaceManifest(manifest, databasePath); const root = rootFromManifest(database, manifest, parsed, databasePath); if (root.rootId !== rootId) { - corrupt(databasePath, "one of its retained Workspace root identities does not match its bytes"); + corrupt(databasePath, "one of its saved Workspace root identities does not match its bytes"); } return root; } @@ -526,7 +526,7 @@ function rootFromManifest( manifests.set(entry.manifest, manifest); } if (entry.size !== manifest.size) { - corrupt(databasePath, "a retained file size differs from its DOFS manifest"); + corrupt(databasePath, "a saved file size differs from its DOFS manifest"); } } } @@ -561,7 +561,7 @@ function validateFile( let manifestHash = node.manifestHash; if (manifestHash === null) { if (!materializeMissingManifest) { - corrupt(databasePath, "a Workspace file has no retained DOFS manifest"); + corrupt(databasePath, "a Workspace file has no saved DOFS manifest"); } manifestHash = buildManifest( dofs, @@ -608,7 +608,7 @@ function validateDofsContentStore(database: DatabaseSync, databasePath: string): ); } if (count(database, "vfs_blob_bytes", databasePath) !== blobs.length) { - corrupt(databasePath, "the DOFS blob index and retained bytes are incomplete"); + corrupt(databasePath, "the DOFS blob index and saved bytes are incomplete"); } for (const row of reading(database, "SELECT hash FROM vfs_manifests ORDER BY hash").all()) { @@ -685,7 +685,7 @@ function readNode( corrupt(databasePath, "a Workspace node revision is ahead of the filesystem revision"); } if (row["mount_root"] !== null || row["stub_size"] !== null) { - corrupt(databasePath, "its retained Workspace contains unsupported mount bookkeeping"); + corrupt(databasePath, "its saved Workspace contains unsupported mount bookkeeping"); } const result: NodeRow = { inode: nonnegative(row["inode"], databasePath, "Workspace inode"), @@ -798,7 +798,7 @@ function validateDofsBookkeeping(database: DatabaseSync, databasePath: string): corrupt(databasePath, "its Workspace synchronization cursor is malformed"); } if (count(database, "_vfs_mounts", databasePath) !== 0) { - corrupt(databasePath, "its retained Workspace contains an unsupported mount"); + corrupt(databasePath, "its saved Workspace contains an unsupported mount"); } for (const row of reading( @@ -834,7 +834,7 @@ function requireReferenceSet( .map((row) => toHex(bytes(row[column], databasePath, `${table}.${column}`))) .sort(compareUtf8); if (!equalStrings(actual, expected)) { - corrupt(databasePath, `a retained Workspace root has an inexact ${table} reference set`); + corrupt(databasePath, `a saved Workspace root has an inexact ${table} reference set`); } } diff --git a/packages/workflow/src/fork.ts b/packages/workflow/src/fork.ts index 284192d1e..01f01d202 100644 --- a/packages/workflow/src/fork.ts +++ b/packages/workflow/src/fork.ts @@ -1,8 +1,8 @@ /** - * Which retained events a fork inherits, and what its own journal begins with. + * Which saved events a fork inherits, and what its own journal begins with. * * A fork is a new run whose history starts as somebody else's. `--at` names one - * committed event of the source, and everything the source retained up to and + * committed event of the source, and everything the source saved up to and * including it becomes the fork's inherited prefix. * * ## Two head events are the fork's own @@ -26,7 +26,7 @@ * * ## Selection is total before anything is created * - * A checkpoint nobody retained, a checkpoint whose prefix is not forkable, and + * A checkpoint nobody saved, a checkpoint whose prefix is not forkable, and * a checkpoint at the run's own outcome are all refused here, where refusing * costs nothing. The last of those is not a technicality: a run whose canonical * outcome is inside the prefix has nothing left to continue, and a fork of it @@ -51,7 +51,7 @@ const ROOT_COROUTINE = "root"; const IMPORT_COMPONENT = "import_component"; const ROOT_DOCUMENT = "__root__"; -/** One retained row, as fork selection reads it. */ +/** One saved row, as fork selection reads it. */ export interface ForkCandidate { readonly eventId: string; readonly event: DurableEvent; @@ -59,9 +59,9 @@ export interface ForkCandidate { readonly forkability: Forkability; } -/** What one checkpoint selects out of a source run's retained history. */ +/** What one checkpoint selects out of a source run's saved history. */ export interface ForkSelection { - /** The events the fork inherits, in retained order, without the source's own record. */ + /** The events the fork inherits, in saved order, without the source's own record. */ readonly inherited: readonly ForkCandidate[]; /** The Workspace root the checkpoint event was written against. */ readonly checkpointWorkspaceRootId: string; @@ -81,7 +81,7 @@ export function selectForkPrefix( if (at === -1) { return Err( new WorkflowRequestError( - `no retained event of this run is ${JSON.stringify(checkpointEventId)}. ` + + `no saved event of this run is ${JSON.stringify(checkpointEventId)}. ` + "`xmd workflow history --forkable` lists the checkpoints a fork may select.", ), ); @@ -89,7 +89,7 @@ export function selectForkPrefix( const checkpoint = candidates[at]; if (checkpoint === undefined) { - return Err(new WorkflowRequestError("the selected checkpoint is not a retained event")); + return Err(new WorkflowRequestError("the selected checkpoint is not a saved event")); } if (isRootOutcome(checkpoint.event)) { diff --git a/packages/workflow/src/journal.ts b/packages/workflow/src/journal.ts index 0d671a117..a913ae541 100644 --- a/packages/workflow/src/journal.ts +++ b/packages/workflow/src/journal.ts @@ -22,9 +22,9 @@ import type { DurableEvent, EffectDescription } from "@executablemd/durable-stre /** * One workflow run. * - * No base and no pinned commit: a run starts from exact retained bytes rather + * No base and no pinned commit: a run starts from exact saved bytes rather * than from a repository state, and a member naming one would be an assertion - * about a repository the run never had. The bundle hash is what the retained + * about a repository the run never had. The bundle hash is what the saved * definition is, and the exact target is what distinguishes two runs over it. * * The value a journal records, and the value `getWorkflowRun()` answers with. @@ -48,7 +48,7 @@ const RUN_TARGET = "targetPath"; * * Object-member order is presentation and not identity — a canonical-JSON * container sorts these keys under its own rule without changing the value — - * but one ordinary spelling is what keeps a retained record byte for byte what + * but one ordinary spelling is what keeps a saved record byte for byte what * it always was. */ export function workflowRunValue(run: WorkflowRun): Record { @@ -125,7 +125,7 @@ export function readWorkflowRun(value: unknown): WorkflowRun | undefined { // The exact member set, in any order, with the target the one optional // addition. Nothing in between: a value carrying a member this shape does not // declare is not a run read loosely — it is a value this build cannot account - // for. A record retained under an older format carried `definitionVersion`, + // for. A record saved under an older format carried `definitionVersion`, // `base` or `pinnedCommit`, and is refused here for exactly that reason. const names = Object.keys(record); const targeted = names.length === RUN_MEMBERS.length + 1; @@ -152,7 +152,7 @@ export function readWorkflowRun(value: unknown): WorkflowRun | undefined { return Object.freeze({ runId, bundleHash, targetPath }); } -/** What one retained event records about the run, when it records anything. */ +/** What one saved event records about the run, when it records anything. */ interface RunClaim { readonly settled: boolean; readonly value: unknown; @@ -199,7 +199,7 @@ function runClaim(event: DurableEvent): RunClaim | undefined { * What one installation requires of a history that is not empty. * * The two installations differ in one thing, and it is not strictness. A - * retained run was created by a host before anything executed, so a history of + * saved run was created by a host before anything executed, so a history of * its own is something it must have: none, or one that failed, means the * recorded work is not this run's. A programmatic run allocates itself on first * execution, and §6 records a base that would not resolve as a *failed* effect — @@ -214,7 +214,7 @@ export interface RunHistoryRules { } /** - * Hold a retained history to the run it belongs to. + * Hold a saved history to the run it belongs to. * * An empty history is the ordinary live start and is held to nothing. Otherwise * every canonical record it carries must read as a workflow run and agree with @@ -222,16 +222,16 @@ export interface RunHistoryRules { * the root document is imported, so two describe two runs. */ export function admitWorkflowRunHistory( - retained: readonly DurableEvent[], + saved: readonly DurableEvent[], rules: RunHistoryRules, ): WorkflowRun | undefined { - if (retained.length === 0) { + if (saved.length === 0) { return undefined; } const runs: WorkflowRun[] = []; let claimed = 0; - for (const event of retained) { + for (const event of saved) { const claim = runClaim(event); if (claim === undefined) { continue; @@ -309,17 +309,17 @@ export function duplicateRunRecords(records: number): StaleInputError { } /** - * The recorded run is not the retained run this execution was installed with. + * The recorded run is not the saved run this execution was installed with. * * The differing fields are named and their values are not. A run id may be * caller-selected and a base may be any revision expression, so both are - * external text on the same terms as retained props: naming a field says what + * external text on the same terms as saved props: naming a field says what * disagrees without carrying the disagreement into logs and rendered output. */ -export function retainedRunMismatch(fields: readonly string[]): StaleInputError { +export function savedRunMismatch(fields: readonly string[]): StaleInputError { return new StaleInputError( - `The journal records a workflow run whose ${fields.join(", ")} differs from the retained ` + - "run this execution was installed with. A retained run is replayed as itself rather " + + `The journal records a workflow run whose ${fields.join(", ")} differs from the saved ` + + "run this execution was installed with. A saved run is replayed as itself rather " + "than onto a different one. Resume the run the journal belongs to.", { coroutineId: ROOT_COROUTINE, description: refusalDescription() }, ); diff --git a/packages/workflow/src/lifecycle/api.ts b/packages/workflow/src/lifecycle/api.ts index 5e213e551..865efe013 100644 --- a/packages/workflow/src/lifecycle/api.ts +++ b/packages/workflow/src/lifecycle/api.ts @@ -13,7 +13,7 @@ * executor lock. It registered that object for one run and one acquisition and * checks it by identity — together with its open scope and run — inside every * mutating transaction. A run id, a database handle, a structural look-alike or - * a retained token authorizes nothing. + * a saved token authorizes nothing. * * ## Inspection returns values * @@ -80,7 +80,7 @@ export interface WorkflowForkLineage { * Every member here is true of the run. What is deliberately *not* here is * retrieval metadata: where a definition could be fetched from now is a fact * about the machine holding the run rather than about the run, so it belongs to - * the retained-run snapshot alone and is not a key of this type at all. A + * the saved-run snapshot alone and is not a key of this type at all. A * sealed artifact projects these members and nothing else, and stating that as * a shared base is what makes "an artifact carries no retrieval" checkable * rather than a promise about what one implementation happens to write. @@ -88,7 +88,7 @@ export interface WorkflowForkLineage { export interface WorkflowInspectionSnapshot { readonly record: WorkflowRunRecord; readonly executions: readonly DocumentExecutionRecord[]; - /** The last retained event and the root it was associated with. */ + /** The last saved event and the root it was associated with. */ readonly journalFrontier?: { readonly eventId: string; readonly workspaceRootId: string; @@ -99,10 +99,10 @@ export interface WorkflowInspectionSnapshot { } /** - * One retained run's snapshot: what it says about itself, and where this host + * One saved run's snapshot: what it says about itself, and where this host * can find its definition again. * - * `retrieval` is the one member a retained run has and evidence about a run + * `retrieval` is the one member a saved run has and evidence about a run * does not. It is replaceable, credential-free host arrangement — which is * exactly why it stays here rather than in the shared base. */ @@ -133,7 +133,7 @@ export interface WorkflowLifecycleApi { * answer to a question nobody asked. */ list(): Operation>; - /** Every retained event of one run, in append order. */ + /** Every saved event of one run, in append order. */ history(runId: string): Operation>; /** * One sealed artifact's lifecycle snapshot, read from the file at `path`. @@ -145,9 +145,9 @@ export interface WorkflowLifecycleApi { inspectArtifact(path: string): Operation>; /** Every event one sealed artifact retains, in append order. */ historyArtifact(path: string): Operation>; - /** Make one run terminal, following its retained status. */ + /** Make one run terminal, following its saved status. */ cancel(runId: string): Operation>; - /** Remove one run's retained storage. */ + /** Remove one run's saved storage. */ delete(runId: string): Operation>; /** * Seal one committed frontier as a portable artifact, leaving the run as it diff --git a/packages/workflow/src/lifecycle/artifact.ts b/packages/workflow/src/lifecycle/artifact.ts index 516805472..4536b9558 100644 --- a/packages/workflow/src/lifecycle/artifact.ts +++ b/packages/workflow/src/lifecycle/artifact.ts @@ -1,7 +1,7 @@ /** * What inspecting a sealed artifact answers with. * - * A retained run and an artifact are addressed differently on purpose. A run id + * A saved run and an artifact are addressed differently on purpose. A run id * names live lifecycle ownership — something a caller may still advance, cancel * or delete — while an artifact path names immutable evidence that left the * machine which produced it. So they are sibling operations rather than one @@ -10,13 +10,13 @@ * * The values are the same values. An artifact carries the run's own record, its * executions, its journal and the Workspace roots it was associated with, so - * inspection projects exactly the lifecycle and history shapes a retained run + * inspection projects exactly the lifecycle and history shapes a saved run * projects, plus the two facts only an artifact has: which artifact this is, and * the committed boundary it was sealed at. * * Nothing here names a container, a path, a database or a byte. This module is * the provider-neutral half of the boundary: the Deno adapter opens the file and - * verifies it, and hands back these values. The retained record shapes an + * verifies it, and hands back these values. The saved record shapes an * artifact holds — DOFS manifests, DOFS blobs, SQLite rows — stay with that * adapter, because a host that cannot open one has no use for their names. */ @@ -44,7 +44,7 @@ export interface WorkflowArtifactIdentity { * One artifact's snapshot: what the run said about itself, and whose evidence * this is. * - * It extends the shared inspection base rather than the retained-run snapshot, + * It extends the shared inspection base rather than the saved-run snapshot, * so `retrieval` is not a key of this type. That is the whole point: an * artifact excludes where its definition could be fetched from now, because * that is ownership belonging to the machine that exported rather than @@ -59,7 +59,7 @@ export interface WorkflowArtifactSnapshot extends WorkflowInspectionSnapshot { /** * One artifact's history, and whose history it is. * - * An envelope rather than the bare array a retained run answers with, because + * An envelope rather than the bare array a saved run answers with, because * there is nowhere else for the identity and the frontier to go: repeating them * on every row would make one fact into many, and reporting them beside the * result would put them outside the value a caller parses. diff --git a/packages/workflow/src/lifecycle/execution.ts b/packages/workflow/src/lifecycle/execution.ts index 5beff798f..800b84e8d 100644 --- a/packages/workflow/src/lifecycle/execution.ts +++ b/packages/workflow/src/lifecycle/execution.ts @@ -17,7 +17,7 @@ import type { ExecutorLock } from "./api.ts"; import type { WorkflowRunDatabase } from "../storage/api.ts"; import type { JsonObject } from "../storage/members.ts"; import type { SourceBundleSnapshotEntry, WorkflowDefinition } from "../storage/source-bundle.ts"; -import type { RetainedDefinitionSources } from "./source.ts"; +import type { SavedDefinitionSources } from "./source.ts"; import type { DocumentExecutionCompletion, DocumentExecutionRecord, @@ -52,7 +52,7 @@ export interface WorkflowForkSelection { /** * One caller's request to admit a fork and begin its first execution. * - * The creation is the fork's own: its definition, its retained bytes and the + * The creation is the fork's own: its definition, its saved bytes and the * props it was normalized to. What it inherits is decided by the selection, and the source * takes no part in this run's identity beyond being named as its lineage. */ @@ -105,12 +105,12 @@ export interface WorkflowExecutionBegun { * Always present. A source-bundle run's content comes out of its own store; * a Git run's comes through the host's legacy reader, which is consulted * against the descriptor about to be persisted — before persistence — when - * the run is being created, and against the retained descriptor when it + * the run is being created, and against the saved descriptor when it * already exists. A host that installed no reader cannot begin a Git run at * all, which is the honest answer: it has no way to obtain what that run * executes. */ - readonly sources: RetainedDefinitionSources; + readonly sources: SavedDefinitionSources; } /** diff --git a/packages/workflow/src/lifecycle/export.ts b/packages/workflow/src/lifecycle/export.ts index 70c5f82a4..d9cf8258b 100644 --- a/packages/workflow/src/lifecycle/export.ts +++ b/packages/workflow/src/lifecycle/export.ts @@ -38,7 +38,7 @@ * container imports it. Which run, which final committed event and which * Workspace root is what the *lifecycle* decided under the executor lock; the * artifact records that decision. Every member is a string, so this stays where - * the provider-neutral surface can name it — the retained shapes an artifact + * the provider-neutral surface can name it — the saved shapes an artifact * also carries are DOFS and SQLite records that belong to the adapter holding * them. * diff --git a/packages/workflow/src/lifecycle/forkability.ts b/packages/workflow/src/lifecycle/forkability.ts index 0abb95e8e..ef3e563db 100644 --- a/packages/workflow/src/lifecycle/forkability.ts +++ b/packages/workflow/src/lifecycle/forkability.ts @@ -2,10 +2,10 @@ * Whether a run's history can be continued as somebody else's. * * A fork inherits a journal prefix and replays it under a new identity. That - * only works while every retained event in the prefix means the same thing to + * only works while every saved event in the prefix means the same thing to * the fork as it did to the source: a recorded value the fork can consume, and * a Workspace root the fork can be given. An Agent turn whose provider session - * the fork cannot enter, and a retained effect this build has never heard of, + * the fork cannot enter, and a saved effect this build has never heard of, * are events a fork would have to pretend it understood. * * ## An external effect is judged by its record, not by its type @@ -25,14 +25,14 @@ * * ## Recognition is an allowlist * - * A retained effect type is forkable because it is named here, never because it + * A saved effect type is forkable because it is named here, never because it * failed to match something. A build that meets an effect a later build wrote * reports `unsupported-effect` rather than inheriting a record whose meaning it * is guessing at. * * ## What a blocker may say * - * A stable code and an event id. Nothing else: the retained description, the + * A stable code and an event id. Nothing else: the saved description, the * filtered result, the run's props and whatever a provider said about a * session are history this classification reads and never republishes. */ @@ -78,7 +78,7 @@ const INHERITABLE_EFFECTS: ReadonlySet = new Set([ SUSPENSION_REQUEST, SUSPENSION_ANSWER, // Core's own durable operations. Named as the durable record names them, - // because that is what a retained row holds — a build reading history a + // because that is what a saved row holds — a build reading history a // different build wrote has only these strings to go on. "import_component", "eval", @@ -92,7 +92,7 @@ const INHERITABLE_EFFECTS: ReadonlySet = new Set([ "loop", "loop_iteration", // The Workspace effects the local provider records. Every one of them is - // content inside this run's database, and a retained root carries it. + // content inside this run's database, and a saved root carries it. "workspace_file", "workspace_repository", "workspace_worktree", @@ -102,7 +102,7 @@ const INHERITABLE_EFFECTS: ReadonlySet = new Set([ ]); /** - * Whether a retained Git-host event carries a completed reconciliation record. + * Whether a saved Git-host event carries a completed reconciliation record. * * A failed Git-host effect retains no record — its outcome is the durable * operation's failed result — so there is nothing there a fork could continue @@ -111,7 +111,7 @@ const INHERITABLE_EFFECTS: ReadonlySet = new Set([ * * Read as compatibility data rather than through the feature's own parser. The * effect belongs to `@executablemd/git` now, and this module classifies - * retained history without importing what wrote it. A record that will not + * saved history without importing what wrote it. A record that will not * read as completed is classified `external-state-unavailable`, which is the * conservative answer and the one the total parse also gave. */ @@ -176,7 +176,7 @@ function text(value: unknown): boolean { } /** - * One retained object, when it declares exactly these members. + * One saved object, when it declares exactly these members. * * Exactly, because a member the shape does not declare describes something * else and a fork does not guess at it. A record that is nearly one is still @@ -213,7 +213,7 @@ function exactly(value: unknown, members: readonly string[]): Record; + readonly savedRoots: ReadonlySet; } /** - * Forkability at each event, in the order the events were retained. + * Forkability at each event, in the order the events were saved. * * One entry per candidate, so a caller can read the answer for the exact * checkpoint it is considering without recomputing the prefix. @@ -330,7 +330,7 @@ function blockersOf( context: ForkabilityContext, ): readonly ForkBlockerCode[] { const codes: ForkBlockerCode[] = []; - if (!context.retainedRoots.has(candidate.workspaceRootId)) { + if (!context.savedRoots.has(candidate.workspaceRootId)) { codes.push("workspace-root-unavailable"); } const { event } = candidate; diff --git a/packages/workflow/src/lifecycle/history.ts b/packages/workflow/src/lifecycle/history.ts index 675460f77..3882dd44e 100644 --- a/packages/workflow/src/lifecycle/history.ts +++ b/packages/workflow/src/lifecycle/history.ts @@ -1,8 +1,8 @@ /** - * One retained protocol row, as history reads it. + * One saved protocol row, as history reads it. * * History is the run's record, not a reconstruction of it. An entry holds the - * event exactly as the journal filtered and retained it, the opaque id that row + * event exactly as the journal filtered and saved it, the opaque id that row * keeps, and the Workspace root the row was already associated with. Nothing * here materializes a root, reopens a definition or asks a provider anything. * @@ -13,7 +13,7 @@ * from an expansion id, an effect name or the current source. A field that is * present and does not parse makes the entry unreadable — reporting it as * source-less would describe history the run does not hold — and the diagnostic - * repeats no part of the value, which is retained filtered data. + * repeats no part of the value, which is saved filtered data. */ import type { DurableEvent } from "@executablemd/durable-streams"; @@ -29,7 +29,7 @@ export interface WorkflowHistoryEntry { readonly eventId: string; readonly event: DurableEvent; readonly workspaceRootId: string; - /** Where the authored operation was written, when the event retained it. */ + /** Where the authored operation was written, when the event saved it. */ readonly source?: Readonly; /** Whether a fork may select this event, and what stands in the way. */ readonly forkability: Forkability; @@ -46,7 +46,7 @@ export interface InheritedEventProvenance { const MEMBERS = ["path", "generatedSource", "offset", "line", "column"]; /** - * The authored position an event retained, or none when it retained none. + * The authored position an event saved, or none when it saved none. * * A Close carries no description at all, and a trusted-host Yield may carry one * without this field. Both are ordinary absence. @@ -122,7 +122,7 @@ function fail(reason: string, path: string): Error { */ export interface HistoryProjectionContext { /** Every Workspace root the source still holds. */ - readonly retainedRoots: ReadonlySet; + readonly savedRoots: ReadonlySet; /** Where each inherited row came from. Rows a run wrote itself are absent. */ readonly inherited: ReadonlyMap; } @@ -140,9 +140,9 @@ const UNCLASSIFIED: Forkability = Object.freeze({ }); /** - * Ordered retained rows, as the public history they project to. + * Ordered saved rows, as the public history they project to. * - * Everything history adds to a retained row happens here: the authored source + * Everything history adds to a saved row happens here: the authored source * is parsed out of the event, forkability is classified cumulatively over the * whole ordered prefix, inherited provenance is attached where there is any, * and each entry is frozen. @@ -151,7 +151,7 @@ export function projectHistory( candidates: readonly ForkabilityCandidate[], context: HistoryProjectionContext, ): readonly WorkflowHistoryEntry[] { - const forkability = classifyForkability(candidates, { retainedRoots: context.retainedRoots }); + const forkability = classifyForkability(candidates, { savedRoots: context.savedRoots }); return Object.freeze( candidates.map((candidate, index) => Object.freeze({ diff --git a/packages/workflow/src/lifecycle/source.ts b/packages/workflow/src/lifecycle/source.ts index eb43f7ed5..adc363d92 100644 --- a/packages/workflow/src/lifecycle/source.ts +++ b/packages/workflow/src/lifecycle/source.ts @@ -1,12 +1,12 @@ /** - * The Markdown a retained definition names. + * The Markdown a saved definition names. * * A run's definition is identity; its source is content. A definition keeps * both, so its content comes out of the run's own store and nothing has to be * fetched to execute it. No host supplies a source capability, because there is - * no definition whose bytes live anywhere but in the run that retained them. + * no definition whose bytes live anywhere but in the run that saved them. * - * The retained lifecycle reads that store and holds what it finds to the + * The saved lifecycle reads that store and holds what it finds to the * descriptor it asked about, which is what makes "these are the bytes this run * is a run of" a verifiable fact rather than an assertion by whoever did the * reading. @@ -14,8 +14,8 @@ import type { WorkflowDefinition } from "../storage/source-bundle.ts"; -/** One retained source: its logical path and its exact bytes. */ -export interface RetainedSource { +/** One saved source: its logical path and its exact bytes. */ +export interface SavedSource { readonly path: string; readonly bytes: Uint8Array; } @@ -25,9 +25,9 @@ export interface RetainedSource { * * The bytes are the store's own, read back after they were committed rather * than the buffers a caller offered — so what executes is what the run - * retained, not what somebody still holds a reference to. + * saved, not what somebody still holds a reference to. */ -export interface RetainedDefinitionSources { +export interface SavedDefinitionSources { readonly definition: WorkflowDefinition; - readonly sources: readonly RetainedSource[]; + readonly sources: readonly SavedSource[]; } diff --git a/packages/workflow/src/run.ts b/packages/workflow/src/run.ts index 72ba7041f..52c1a8093 100644 --- a/packages/workflow/src/run.ts +++ b/packages/workflow/src/run.ts @@ -25,7 +25,7 @@ * stored result without entering the durable body at all, so preparation is * never reached and the admission is what restores the run. * - * `retainedWorkflowInstallation(run)` is the same three states under a run that already + * `savedWorkflowInstallation(run)` is the same three states under a run that already * exists. A workflow host has created the storage record before anything * executes, so the live path records exactly the value it was given rather than * allocating an id and resolving a base, and every state requires the journal to @@ -42,9 +42,9 @@ * So this is an *installation* the trusted host hands to `executeInstalled()`, * and core owns the read. The admission is captured before any installation, * middleware or document code exists, and applied inside the same trusted - * `readAll` that already holds a resumed run to its recorded root selection. That read happens before any middleware runs, on the retained + * `readAll` that already holds a resumed run to its recorded root selection. That read happens before any middleware runs, on the saved * snapshot every later phase consumes — ahead of public guard policy, of any - * retained Yield reaching execution, of a retained `Close` being reused, of + * saved Yield reaching execution, of a saved `Close` being reused, of * authored work, and of any append. Public `ReplayGuard` handlers still observe * and may still reject the history this admits; none of them can widen it. * @@ -71,7 +71,7 @@ import type { import type { ExecutionInstallation, JournalAdmission } from "@executablemd/core/host"; import { admitWorkflowRunHistory, - retainedRunMismatch, + savedRunMismatch, describeWorkflowRun, malformedRecord, readWorkflowRun, @@ -101,7 +101,7 @@ const CurrentWorkflowRun: Context = createContext 0) { - throw retainedRunMismatch(differing); + throw savedRunMismatch(differing); } return recorded; }, @@ -195,7 +195,7 @@ function retaining(run: WorkflowRun): WorkflowRunPreparation { } /** - * Which terms of the retained run a recorded one disagrees with. + * Which terms of the saved run a recorded one disagrees with. * * Every term is compared, the exact target included: absent equals only absent, * which is what keeps a whole-document run distinct from a targeted one. @@ -214,7 +214,7 @@ function differingFields(recorded: WorkflowRun, expected: WorkflowRun): string[] * One read, nothing else inside — the same narrowness the journal's own reads * are held to, for the same reason. */ -function readingRetainedValue(read: () => T): T | undefined { +function readingSavedValue(read: () => T): T | undefined { try { return read(); } catch { @@ -238,7 +238,7 @@ function same(left: WorkflowRun, right: WorkflowRun): boolean { /** * Bring the run into being, inside the durable root. * - * Canonical core invokes this after retained-history admission and before any + * Canonical core invokes this after saved-history admission and before any * public `Execution.document` policy, the root import, and every authored * effect — so the run exists before anything can observe it, and nothing a * handler does can prevent it, replace it, or reach the journal ahead of it. @@ -273,7 +273,7 @@ function* prepare(preparation: WorkflowRunPreparation): Workflow { * What this installation requires of the history a document execution replays. * * Contributed to core rather than wrapped around core. The comparison runs - * inside the execution's own trusted journal read, on the retained snapshot + * inside the execution's own trusted journal read, on the saved snapshot * every later phase consumes — so no `ReplayGuard` handler that declines to * delegate, and no `Execution` handler that rebuilds the options a later one * produced, can suppress, replace or reorder it. By the time any middleware @@ -284,11 +284,11 @@ function* prepare(preparation: WorkflowRunPreparation): Workflow { * inside the execution where the run a recorded result belongs to is known. */ function admits(preparation: WorkflowRunPreparation): JournalAdmission { - return function* (retained: readonly DurableEvent[]): Operation { + return function* (saved: readonly DurableEvent[]): Operation { // What the history is held to is decided by the captured `preparation`, and // by nothing that is read here. The slot is reached only afterwards, to // publish what was already admitted. - const admitted = admitWorkflowRunHistory(retained, preparation); + const admitted = admitWorkflowRunHistory(saved, preparation); if (admitted === undefined) { return; } @@ -357,12 +357,12 @@ export function createWorkflowRunInstallation( * * Git is not consulted, and no identifier is generated. */ -export function retainedWorkflowInstallation(run: WorkflowRun): ExecutionInstallation { - return createWorkflowRunInstallation(retaining(retainedRun(run))); +export function savedWorkflowInstallation(run: WorkflowRun): ExecutionInstallation { + return createWorkflowRunInstallation(retaining(savedRun(run))); } /** - * The retained run as a frozen value of its own. + * The saved run as a frozen value of its own. * * Parsed rather than believed: it arrives from a storage record a host read * back, so a member that is missing or empty is a value that identifies no run @@ -370,21 +370,21 @@ export function retainedWorkflowInstallation(run: WorkflowRun): ExecutionInstall * here, so a host handing over a wider record installs the run it describes * rather than being refused for carrying its own bookkeeping. */ -function retainedRun(run: WorkflowRun): WorkflowRun { +function savedRun(run: WorkflowRun): WorkflowRun { // Named through the same total read as a journal value: a host that hands // over a record whose members refuse to be read has supplied a value that // identifies no run, which is the sentence below rather than its exception. - const parsed = readWorkflowRun(readingRetainedValue(() => workflowRunValue(run))); + const parsed = readWorkflowRun(readingSavedValue(() => workflowRunValue(run))); if (parsed === undefined || parsed.runId === "") { throw new Error( - "retainedWorkflowInstallation() needs a complete retained run: its id and its bundle hash. " + + "savedWorkflowInstallation() needs a complete saved run: its id and its bundle hash. " + "A run installed without them identifies no workflow run.", ); } if (parsed.bundleHash === "") { throw new Error( - "retainedWorkflowInstallation() needs the retained run's bundle hash: an empty one pins the " + - "run to no retained source at all.", + "savedWorkflowInstallation() needs the saved run's bundle hash: an empty one pins the " + + "run to no saved source at all.", ); } return parsed; diff --git a/packages/workflow/src/storage/api.ts b/packages/workflow/src/storage/api.ts index 7b5f527c0..0da5f05f7 100644 --- a/packages/workflow/src/storage/api.ts +++ b/packages/workflow/src/storage/api.ts @@ -58,7 +58,7 @@ import type { DefinitionRetrieval, DocumentExecutionRecord, WorkflowRunRecord } * transition instead, which takes the complete snapshot with the descriptor. */ export interface CreateWorkflowRunRequest { - /** The public run id. Retained inside the run, and the only way back to it. */ + /** The public run id. Saved inside the run, and the only way back to it. */ readonly runId: string; /** The immutable definition this run is a run of. */ readonly definition: WorkflowDefinition; @@ -94,7 +94,7 @@ export interface WorkflowRunTransaction { } /** - * One retained event together with the id it keeps and the root it was written + * One saved event together with the id it keeps and the root it was written * against. * * `DurableStream` carries events and not their identities, and a stop reason @@ -131,7 +131,7 @@ export interface WorkflowRunDatabase { /** The run's filtered journal. An append here commits on its own. */ readonly journal: DurableStream; - /** Every retained event with its opaque id, in append order. */ + /** Every saved event with its opaque id, in append order. */ readJournalEntries(): Operation>; /** diff --git a/packages/workflow/src/storage/compatibility.ts b/packages/workflow/src/storage/compatibility.ts index 18c66f14f..4f340935e 100644 --- a/packages/workflow/src/storage/compatibility.ts +++ b/packages/workflow/src/storage/compatibility.ts @@ -27,7 +27,7 @@ export interface WorkflowRunComparison { * The immutable fields in which a stored run and a request disagree. * * Empty means the request addresses the stored run. Field names are reported, - * never the values behind them: props are retained history, and a conflict is + * never the values behind them: props are saved history, and a conflict is * not a reason to print them. */ export function conflictingFields( @@ -65,7 +65,7 @@ export function conflictingFields( * * The whole bundle is compared, not just the hash that commits to it. The * bundle hash already names this manifest, and it is compared too — but a hash - * is not a reason to admit a retained structure that disagrees with itself, and + * is not a reason to admit a saved structure that disagrees with itself, and * a stored descriptor whose manifest no longer matches its hash must not be * reused merely because a candidate carried the same hash. */ @@ -104,7 +104,7 @@ function sameSources( * descriptors were parsed in. * * A component the run no longer declares and a name pointed at a different - * retained source are the same fact: this request asks for code the stored run + * saved source are the same fact: this request asks for code the stored run * is not a run of. Reusing the run id would resume a procedure under components * it never executed. */ diff --git a/packages/workflow/src/storage/definition.ts b/packages/workflow/src/storage/definition.ts index 084874506..8333ba5ad 100644 --- a/packages/workflow/src/storage/definition.ts +++ b/packages/workflow/src/storage/definition.ts @@ -34,7 +34,7 @@ export type { SourceBundleComponent, WorkflowDefinition }; * host that builds one by hand — or reads one from a file — can build one that * type-checks and does not describe a definition. The parser is closed, so a * value carrying any member this shape does not declare is refused rather than - * read loosely. A record retained under an older format carried a `version` + * read loosely. A record saved under an older format carried a `version` * member, and is therefore refused here rather than guessed at. */ export function parseWorkflowDefinition(value: unknown): Result { diff --git a/packages/workflow/src/storage/errors.ts b/packages/workflow/src/storage/errors.ts index 6d128db94..27eb218bd 100644 --- a/packages/workflow/src/storage/errors.ts +++ b/packages/workflow/src/storage/errors.ts @@ -8,7 +8,7 @@ * seven different situations, and collapsing them would leave a host guessing * whether to create, refuse, or report damage. * - * None of these messages quotes a stored value. Retained props and journal + * None of these messages quotes a stored value. Saved props and journal * payloads are filtered history, and a storage failure is not a reason to copy * them into an error that travels to logs and terminals. A conflict names the * fields that differ; the run id and the host path are named because the caller @@ -151,7 +151,7 @@ export class WorkflowDatabaseCorruptError extends WorkflowStorageError { * Inspection reads a database a lost host left mid-transaction by recovering a * copy of it, so this says the copy could not be produced or could not be * cleaned up — never that the run itself is damaged. Damage keeps its own - * types. The retained database and its journal are unchanged either way. + * types. The saved database and its journal are unchanged either way. * * `scratchPath` is absent whenever cleanup succeeded, which is every failure * except one. It is populated only when the private copy could not be removed, @@ -160,7 +160,7 @@ export class WorkflowDatabaseCorruptError extends WorkflowStorageError { export class WorkflowInspectionRecoveryError extends WorkflowStorageError { override name = "WorkflowInspectionRecoveryError"; - /** The retained run database inspection was asked about. */ + /** The saved run database inspection was asked about. */ readonly path: string; /** The private directory that still holds a copy, when removal failed. */ @@ -243,7 +243,7 @@ export class WorkflowDatabaseClosedError extends WorkflowStorageError { } /** - * A version-2 run's own retained source is not there. + * A version-2 run's own saved source is not there. * * Its own category because there is nothing to fall back to and nothing to * repair: the descriptor names a manifest entry or a blob the live store does @@ -264,11 +264,11 @@ export class WorkflowDefinitionSourceMissingError extends WorkflowStorageError { } /** - * A version-2 run's retained source is there and does not describe itself. + * A version-2 run's saved source is there and does not describe itself. * * Distinct from absence because an operator acts on it differently. A path, a * length, a source hash, a component mapping or the bundle hash disagrees with - * what the descriptor states, so what is retained is no longer evidence of the + * what the descriptor states, so what is saved is no longer evidence of the * definition it names. No partial closure is returned. */ export class WorkflowDefinitionCorruptError extends WorkflowStorageError { @@ -276,7 +276,7 @@ export class WorkflowDefinitionCorruptError extends WorkflowStorageError { constructor(reason: string) { super( - `This workflow run's retained source bundle disagrees with its own descriptor: ${reason}. ` + + `This workflow run's saved source bundle disagrees with its own descriptor: ${reason}. ` + "No partial source is returned and the run is left unchanged.", ); } @@ -321,7 +321,7 @@ export class WorkflowExportBusyError extends WorkflowStorageError { * The base of every expected XMD artifact outcome. * * An artifact is discovered from the path a caller supplied and from nothing - * else, so every one of these names that path. None of them quotes a retained + * else, so every one of these names that path. None of them quotes a saved * byte: an artifact holds whatever the run wrote into its Workspace, and a * refusal that repeated part of it would publish the content the file was * being refused for. @@ -441,7 +441,7 @@ export class XmdArtifactSchemaError extends XmdArtifactError { } } -/** A retained record does not describe what its kind declares. */ +/** A saved record does not describe what its kind declares. */ export class XmdArtifactRecordError extends XmdArtifactError { override name = "XmdArtifactRecordError"; diff --git a/packages/workflow/src/storage/record.ts b/packages/workflow/src/storage/record.ts index 0f8633b0a..b37269bdb 100644 --- a/packages/workflow/src/storage/record.ts +++ b/packages/workflow/src/storage/record.ts @@ -42,7 +42,7 @@ export type WorkflowRunStatus = | "failed" | "cancelled"; -/** Every retained status, in the order the specification lists them. */ +/** Every saved status, in the order the specification lists them. */ export const WORKFLOW_RUN_STATUSES: readonly WorkflowRunStatus[] = Object.freeze([ "running", "suspended", @@ -58,16 +58,16 @@ export const WORKFLOW_RUN_STATUSES: readonly WorkflowRunStatus[] = Object.freeze * A host reason carries a categorical code the host assigns. A journal reason * points at an event that already crossed the secret filter. Neither shape can * hold an arbitrary exception message, because a message that reached storage - * this way would be retained history nothing had filtered. + * this way would be saved history nothing had filtered. */ export type WorkflowStopReason = | { readonly kind: "host"; readonly code: string } | { readonly kind: "journal"; readonly eventId: string }; /** - * One workflow run's retained metadata. + * One workflow run's saved metadata. * - * No `base` and no pinned commit. A run starts from exact retained bytes rather + * No `base` and no pinned commit. A run starts from exact saved bytes rather * than from a repository state, so a member naming one would be an assertion * about a repository this run never had — read back by anything comparing * identity as though the run had named it. @@ -86,7 +86,7 @@ export interface WorkflowRunRecord { * Where this host can fetch the definition from, as of now. * * Replaceable, excluded from identity, and never a credential. A host - * reauthorizes before using it rather than treating a retained locator as + * reauthorizes before using it rather than treating a saved locator as * permission it already has. */ export interface DefinitionRetrieval { @@ -121,8 +121,8 @@ export interface StoredRunState { * The run id a value names. * * One rule, because a run id is one thing wherever it turns up. A caller's - * argument and a retained `run_id` column are checked against exactly this, so - * an id no request could ever address cannot be retained and then read back as + * argument and a saved `run_id` column are checked against exactly this, so + * an id no request could ever address cannot be saved and then read back as * though it addressed something. What differs is only how each side reports a * refusal, which is what `fail` decides: a caller asked for something * impossible, and storage holds something that does not parse. @@ -136,7 +136,7 @@ export function parseRunId(value: unknown, path: string, fail: Fail): string { } if (value.includes("\u0000")) { throw fail( - "a run id cannot contain a NUL: a retained id is compared as text up to one, so two " + + "a run id cannot contain a NUL: a saved id is compared as text up to one, so two " + "ids that differ only past it would address the same run", path, ); diff --git a/packages/workflow/src/storage/source-bundle.ts b/packages/workflow/src/storage/source-bundle.ts index a0e6b8ab1..4d56cf740 100644 --- a/packages/workflow/src/storage/source-bundle.ts +++ b/packages/workflow/src/storage/source-bundle.ts @@ -3,9 +3,9 @@ * * A definition identifies the exact bytes, so a run of a file outside a * repository, of an untracked file, or of a file edited since its last commit - * is one immutable definition the moment it is retained. Nothing is fetched + * is one immutable definition the moment it is saved. Nothing is fetched * from anywhere to execute one: the closure a run needs is the closure it - * retained, which is why no host has to supply a reader to begin a run. + * saved, which is why no host has to supply a reader to begin a run. * * There is one definition shape and it carries no version. Nothing released * names an older one, so a later format is a clean break rather than a second @@ -45,7 +45,7 @@ import { * `sources` is the complete closure the run executes, in canonical UTF-8 byte * order of `path`, with `entrypoint` naming exactly one of them. `components` * maps authored component names onto members of that closure, so adding, - * removing or changing a retained dependency changes `bundleHash`. + * removing or changing a saved dependency changes `bundleHash`. */ export interface WorkflowDefinition { readonly hashAlgorithm: "sha256"; @@ -57,14 +57,14 @@ export interface WorkflowDefinition { readonly components?: readonly SourceBundleComponent[]; } -/** One retained source: its logical path, and what its bytes hash and weigh. */ +/** One saved source: its logical path, and what its bytes hash and weigh. */ export interface SourceBundleEntry { readonly path: string; readonly sourceHash: string; readonly byteLength: number; } -/** One authored component name, resolved onto a retained source. */ +/** One authored component name, resolved onto a saved source. */ export interface SourceBundleComponent { readonly name: string; readonly path: string; @@ -218,7 +218,7 @@ function parseSourceEntry(value: unknown, path: string): SourceBundleEntry { * * Absence identifies a definition that declares no workflow components; an * empty array is not a second spelling of it and is refused. Every path names a - * retained source, so the mapping is closed over the same bytes the run + * saved source, so the mapping is closed over the same bytes the run * executes rather than over something a later resolution would have to find. */ function parseComponents( @@ -474,7 +474,7 @@ export function* sourceBundleHash(identity: SourceBundleIdentity): Operation { function refusal(subject: string): string { return ( `The source snapshot offered for this workflow run does not match ${subject} its ` + - "definition declares. Nothing was retained." + "definition declares. Nothing was saved." ); } diff --git a/packages/workflow/src/suspension/answer.ts b/packages/workflow/src/suspension/answer.ts index 719b209f6..f6821522b 100644 --- a/packages/workflow/src/suspension/answer.ts +++ b/packages/workflow/src/suspension/answer.ts @@ -18,7 +18,7 @@ * * ## Publication is the provider's, inside the provider's transaction * - * Retained delivery state and the answer event have to move together: an answer + * Saved delivery state and the answer event have to move together: an answer * consumed without an event would be lost, and an event published without the * consumption could be delivered twice. Neither is something this module can * arrange, because only the host that keeps the run has its transaction. So the @@ -45,7 +45,7 @@ import { } from "@executablemd/durable-streams"; import { WorkflowSuspensionRequestError, type WorkflowSuspensionRequest } from "./api.ts"; -/** The effect type one delivered answer is retained under. */ +/** The effect type one delivered answer is saved under. */ export const SUSPENSION_ANSWER = "suspension_answer"; /** What a live answer publication is given, and what it may do with it. */ @@ -60,16 +60,16 @@ export interface SuspensionAnswerPublication { * Publish the durable answer Yield carrying this value. * * Called inside the owner's own transaction, so the event and whatever - * retained state it consumes commit together or not at all. + * saved state it consumes commit together or not at all. */ publish(value: Json): Operation; } -/** Whoever keeps a run's retained delivery state. */ +/** Whoever keeps a run's saved delivery state. */ export interface SuspensionAnswerProvider { /** * The value that ends this wait, already published, or nothing when none is - * retained. + * saved. * * Returning a value without having published it is a defect rather than an * answer, and is refused: what makes a wait over is the event, not the @@ -127,7 +127,7 @@ const answerProviders = (() => { })(); /** - * Install the owner of this run's retained delivery state for the current scope. + * Install the owner of this run's saved delivery state for the current scope. * * `{ at: "min" }` for the reason every provider here uses it: middleware at the * default position runs outermost, so an outer scope's registration would be @@ -145,10 +145,10 @@ function describeAnswer(id: string): EffectDescription { /** * The wait's answer: replayed when one was published, consumed when one is - * retained, and otherwise the wait itself. + * saved, and otherwise the wait itself. * * `enter` is the public composable route into the wait. It is reached only when - * no answer is retained, and nothing it returns is an answer — a value arriving + * no answer is saved, and nothing it returns is an answer — a value arriving * from it means some handler stood between this operation and the controller, * so the operation does what an unentered wait is and stops. */ @@ -175,7 +175,7 @@ function answerCoordinator( ): Operation { const provider = answerProviders.get(yield* AnswerCoordination.operations.provider); if (provider !== undefined) { - const claimed = yield* claimRetainedAnswer( + const claimed = yield* claimSavedAnswer( provider, id, request, @@ -204,7 +204,7 @@ function answerCoordinator( }; } -function* claimRetainedAnswer( +function* claimSavedAnswer( provider: SuspensionAnswerProvider, id: string, request: WorkflowSuspensionRequest, diff --git a/packages/workflow/src/suspension/api.ts b/packages/workflow/src/suspension/api.ts index 95b326939..b7bb0e74e 100644 --- a/packages/workflow/src/suspension/api.ts +++ b/packages/workflow/src/suspension/api.ts @@ -10,7 +10,7 @@ * * ## Identity is position, not a name * - * The caller selects neither a suspension ID nor a retained answer. The trusted + * The caller selects neither a suspension ID nor a saved answer. The trusted * execution derives one suspension ID from the run and the exact durable * coroutine position, which is what makes the wait a document reaches at one * point in a procedure the same wait it reaches there after a resume — and what @@ -32,7 +32,7 @@ * catch, and a caught suspension would be a document continuing past a wait it * asked for and did not get. * - * Admission is the retained request at the caller's exact current durable + * Admission is the saved request at the caller's exact current durable * position. The identifier presented must be the one this run derives for the * position immediately behind the caller, and the yield there must be that * request, describing what is presented. An identifier from elsewhere in the @@ -96,7 +96,7 @@ export const WorkflowSuspension: Api = createApi { - // The retained value is the suspension ID, and the yield's own result is + // The saved value is the suspension ID, and the yield's own result is // discarded: what this step establishes is that the request is in the // journal, not what it evaluated to. yield createDurableOperation(describeSuspension(id, parsed), function* () { @@ -129,13 +129,13 @@ export function* suspendFor(request: WorkflowSuspensionRequest): Operation * * A durable operation of its own, directly behind the request, so an execution * that already received its answer replays it instead of asking again. Its live - * path reaches the public route only when nothing is retained to answer with, + * path reaches the public route only when nothing is saved to answer with, * and returns only what was published — which is why returning at all means the * wait is over. */ function* publishAnswer(id: string, parsed: WorkflowSuspensionRequest): Workflow { // The protocol hands back what the journal held, which is `unknown` until it - // has been walked. A retained answer reaches the document, so it is parsed + // has been walked. A saved answer reaches the document, so it is parsed // here rather than asserted — the same boundary every restored value passes. const restored: unknown = yield suspensionAnswerEffect(id, parsed, () => WorkflowSuspension.operations.enter(id, parsed), diff --git a/packages/workflow/tests/generated-agent-component.test.ts b/packages/workflow/tests/generated-agent-component.test.ts index 28be4fedb..fcfd1a7e0 100644 --- a/packages/workflow/tests/generated-agent-component.test.ts +++ b/packages/workflow/tests/generated-agent-component.test.ts @@ -455,7 +455,7 @@ describe("Tier WGAC — the registered Evaluate component", () => { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { const database = yield* createRun(); - yield* plant(database, "notes.md", "the retained note\n"); + yield* plant(database, "notes.md", "the saved note\n"); // Two observations with a Workspace write between them. The write // publishes a new root, so the second observation runs against a @@ -474,7 +474,7 @@ describe("Tier WGAC — the registered Evaluate component", () => { // transaction-bound Files provider. Each fragment's read renders its own // text, so the binding holds that text and `` renders it as the // string it is — no name, no envelope, nothing collected beside it. - expect(reported(attempt)).toContain("the retained note"); + expect(reported(attempt)).toContain("the saved note"); expect(reported(attempt)).not.toContain('"name"'); expect(reported(attempt)).not.toContain("observations"); @@ -499,7 +499,7 @@ describe("Tier WGAC — the registered Evaluate component", () => { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { const database = yield* createRun(); - yield* plant(database, "notes.md", "the retained note\n"); + yield* plant(database, "notes.md", "the saved note\n"); // `Evaluate` is a live registered component in this very scope. The // fragment still cannot name it: the evaluator resolves only its own @@ -514,7 +514,7 @@ describe("Tier WGAC — the registered Evaluate component", () => { expect(reported(attempt)).toContain("did not admit"); // One admission, and it refused. No second one from a nested evaluation. expect(admissions(attempt.events)).toHaveLength(1); - expect(reported(attempt)).not.toContain("the retained note"); + expect(reported(attempt)).not.toContain("the saved note"); }); }); @@ -522,7 +522,7 @@ describe("Tier WGAC — the registered Evaluate component", () => { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { const database = yield* createRun(); - yield* plant(database, "notes.md", "the retained note\n"); + yield* plant(database, "notes.md", "the saved note\n"); const attempt = yield* scoped(function* () { // A loaded component that binds the expansion context for its @@ -885,7 +885,7 @@ describe("Tier WGAC — the registered Evaluate component", () => { const resumed = yield* runDocument(database, source, CEILING); expect(resumed.failure).toBe(undefined); - // The first site came back from its own record: its retained read is + // The first site came back from its own record: its saved read is // the only one this run holds, so nothing re-observed the Workspace. expect(reported(resumed)).toContain(ADMITTED_NOTE.trim()); expect(reads(resumed.events)).toEqual(reads(interrupted)); @@ -1089,14 +1089,14 @@ describe("Tier WGAC — the classes an authored element may select", () => { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { const database = yield* createRun(); - yield* plant(database, "notes.md", "the retained note\n"); + yield* plant(database, "notes.md", "the saved note\n"); const attempt = yield* runDocument(database, evaluates(``)); expect(attempt.failure).toBe(undefined); const recorded = admissions(attempt.events); expect(policyOf(recorded[0]!)?.allow).toEqual(["read"]); - // And the write table is not in the retained policy at all, so a run that + // And the write table is not in the saved policy at all, so a run that // changed it has not changed what this admission was granted under. Read // as the identity the write spelling runs under, which is what a row in // that table would hold. @@ -1183,7 +1183,7 @@ describe("Tier WGAC — the standard write table", () => { // One row per selected spelling, in the canonical order the selection is // admitted in: a host's object order is a property of how the selection - // was assembled rather than of what it chose, so the retained rows are + // was assembled rather than of what it chose, so the saved rows are // ordered by name and a continuation compares one directory grant. const policy = policyOf(admissions(attempt.events)[0]!); expect(policy?.allowed).toEqual([ @@ -1321,7 +1321,7 @@ describe("Tier WGAC — the standard write table", () => { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { const database = yield* createRun(); - yield* plant(database, "notes.md", "the retained note\n"); + yield* plant(database, "notes.md", "the saved note\n"); const planted = reads(yield* database.journal.readAll()).length; const attempt = yield* runDocument( @@ -1360,7 +1360,7 @@ describe("Tier WGAC — what a selection binds", () => { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { const database = yield* createRun(); - yield* plant(database, "notes.md", "the retained note\n"); + yield* plant(database, "notes.md", "the saved note\n"); const fragment = `\n\nthe fragment wrote this\n`; const attempt = yield* runDocument( @@ -1374,7 +1374,7 @@ describe("Tier WGAC — what a selection binds", () => { // The read renders its own text and the write renders nothing, so what // the binding holds is exactly the read's output — once, with no entry // for the write and no envelope naming either of them. - expect(bound).toContain("the retained note"); + expect(bound).toContain("the saved note"); expect(bound).not.toContain('"name"'); expect(bound).not.toContain("the fragment wrote this"); expect(yield* stored(database, "/proposed.md")).toBe("the fragment wrote this"); @@ -1397,7 +1397,7 @@ describe("Tier WGAC — a committed mutation is not repeated", () => { const again = yield* runDocument(database, document); expect(again.failure).toBe(undefined); - // Nothing new was journaled and no second mutation happened; the retained + // Nothing new was journaled and no second mutation happened; the saved // content is what the first attempt left. expect(again.events.length).toBe(committed); expect(reads(again.events).length).toBe(written); diff --git a/packages/workflow/tests/generated-observations.test.ts b/packages/workflow/tests/generated-observations.test.ts index 314714347..0e25992a2 100644 --- a/packages/workflow/tests/generated-observations.test.ts +++ b/packages/workflow/tests/generated-observations.test.ts @@ -23,7 +23,7 @@ import { API, FETCH_NOT_PERMITTED_MESSAGE, Permissions } from "@executablemd/run import type { FetchInit, RuntimeFetchResponse } from "@executablemd/runtime"; import { InMemoryStream } from "@executablemd/durable-streams"; import type { DurableEvent, Json } from "@executablemd/durable-streams"; -import { retainedSource, SOURCE_POSITION_FIELD } from "@executablemd/core"; +import { savedSource, SOURCE_POSITION_FIELD } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import { collect } from "@executablemd/core"; import { @@ -37,7 +37,7 @@ import type { RecordedFiles } from "../../core/tests/support/fragment-files.ts"; const ROOT_PATH = "workflows/agent.md"; const URL_ONE = "https://api.example.test/one"; const URL_TWO = "https://api.example.test/two"; -const NOTE = "the retained note\n"; +const NOTE = "the saved note\n"; /** What the substituted transport was asked to do. */ interface Transport { @@ -125,7 +125,7 @@ function once( } try { const output = yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream, includes: [] }, []), + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream, includes: [] }, []), ); return { output: String(output), events: yield* stream.readAll() }; } catch (error) { @@ -271,7 +271,7 @@ describe("Tier WGX — the selection a workflow run offers", () => { const attempt = yield* run(evaluating(`\n`), { files }); expect(attempt.failure).toBe(undefined); - expect(attempt.output).toContain("the retained note"); + expect(attempt.output).toContain("the saved note"); const policy = policyOf(admissions(attempt.events)[0]); // The closed shape this build writes: the marker, the selected classes and // the rows. No Workspace basis and no request inventory — what a run stands @@ -449,7 +449,7 @@ describe("Tier WGX — what an admitted request may reach is live", () => { // this one through carrying `EXTRA, admitted`. expect(server.received).toEqual(["admitted"]); expect(transformed.failure).toContain("not permitted in this scope"); - // What it retained is the refusal, not an observation: the request was + // What it saved is the refusal, not an observation: the request was // refused where it would have been sent, so there is no response here to // restore from. expect(statuses(fetches(transformed.events))).toEqual(["err"]); @@ -498,7 +498,7 @@ describe("Tier WGX — what an admitted request may reach is live", () => { // selection taken from a second reading would send it under no policy at // all. expect(server.asked).toEqual([]); - // The retained reading bounded nothing, so `` was never offered: the + // The saved reading bounded nothing, so `` was never offered: the // fragment is refused at admission, by name, and retains no effect. expect(attempt.failure).toContain("did not admit"); expect(fetches(attempt.events)).toEqual([]); @@ -581,7 +581,7 @@ describe("Tier WGX — a completed effect restores from its record", () => { // the admission's identity. expect(admission.description[SOURCE_POSITION_FIELD]).toMatchObject({ path: ROOT_PATH }); - // A continuation of the retained history: the admission and the nested read + // A continuation of the saved history: the admission and the nested read // replay aligned, so nothing is admitted twice and no read is performed // again. const again = yield* run(source, { events: first.events, files }); diff --git a/packages/workflow/tests/public-entrypoint.test.ts b/packages/workflow/tests/public-entrypoint.test.ts index ddcfe514c..bb6669810 100644 --- a/packages/workflow/tests/public-entrypoint.test.ts +++ b/packages/workflow/tests/public-entrypoint.test.ts @@ -223,7 +223,7 @@ describe("workflow published Deno entrypoint", () => { * Where the Git feature is, and where it is not. * * #443 held one module — `src/run.ts` — to a single named Git import, because - * everything else that retained, recognized, resumed or sealed a run already + * everything else that saved, recognized, resumed or sealed a run already * reached no repository. #822 finishes that: the feature is its own package, so * the rule is no longer "one exception" but "none at all". * diff --git a/packages/workflow/tests/retained-run.test.ts b/packages/workflow/tests/saved-run.test.ts similarity index 86% rename from packages/workflow/tests/retained-run.test.ts rename to packages/workflow/tests/saved-run.test.ts index cbe71e4f9..c16693ea5 100644 --- a/packages/workflow/tests/retained-run.test.ts +++ b/packages/workflow/tests/saved-run.test.ts @@ -4,12 +4,12 @@ * A live execution could be the thing that decides what the run is, allocating * an identifier and establishing a definition as it goes. A workflow host has * already done both by the time a document runs — storage answered with the run - * id, and the definition was established from the bytes it retained — so + * id, and the definition was established from the bytes it saved — so * nothing is left to decide, and a journal that records a different run is not * this run's journal. * * Every test here replaces `Git` with a provider that fails when it is - * consulted, so "the retained installation asks Git nothing" is asserted rather + * consulted, so "the saved installation asks Git nothing" is asserted rather * than assumed. */ @@ -37,13 +37,13 @@ import { import { executeInstalled } from "@executablemd/core/host"; import type { ExecutionRequest } from "@executablemd/core"; import { GitQuery } from "@executablemd/git/api"; -import { getWorkflowRun, retainedWorkflowInstallation } from "../src/run.ts"; +import { getWorkflowRun, savedWorkflowInstallation } from "../src/run.ts"; import type { WorkflowRun } from "../src/run.ts"; const COMMIT = "9fceb02d0ae598e95dc970b74767f19372d61af8"; const OTHER_COMMIT = "1111111111111111111111111111111111111111"; -const RETAINED: WorkflowRun = Object.freeze({ +const SAVED: WorkflowRun = Object.freeze({ runId: "release-1.4", bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", }); @@ -83,7 +83,7 @@ interface Attempt { } /** - * Run `` under a retained installation, watching everything a refusal + * Run `` under a saved installation, watching everything a refusal * has to prevent. * * `seen` is non-empty only if the document expanded; `emitted` is non-empty only @@ -91,7 +91,7 @@ interface Attempt { * policy it wants to prove cannot widen the boundary — installed *outside* the * workflow installation, which is the position a suppressing handler wants. */ -function runRetained( +function runSaved( run: WorkflowRun, stream: DurableStream, install: Operation = ok(), @@ -103,7 +103,7 @@ function runRetained( yield* useForbiddenGit(); yield* useProbe(seen); yield* install; - const workflow = retainedWorkflowInstallation(run); + const workflow = savedWorkflowInstallation(run); yield* after; yield* DocumentOutput.around({ *output([text], next) { @@ -237,7 +237,7 @@ function useForeignStreamHijack(raw: DurableStream): Operation { * * The run id shifts on the second read of the recorded value, which is what a * backend handing out live objects can do. Admission and replay must therefore - * be reading one retained snapshot rather than each taking their own look — + * be reading one saved snapshot rather than each taking their own look — * otherwise a history admitted as one run is replayed as another. */ function shiftingJournal(events: readonly DurableEvent[], reads: string[]): DurableStream { @@ -251,7 +251,7 @@ function shiftingJournal(events: readonly DurableEvent[], reads: string[]): Dura get result(): Result { const runId = reads.length === 0 ? "release-1.4" : "release-1.5"; reads.push(runId); - return { status: "ok", value: { runId, bundleHash: RETAINED.bundleHash } }; + return { status: "ok", value: { runId, bundleHash: SAVED.bundleHash } }; }, }; }; @@ -333,13 +333,13 @@ function completedWith( ); } -describe("Tier RR — retained workflow runs", () => { - it("RR1: records exactly the retained run, without allocating or resolving", function* () { +describe("Tier RR — saved workflow runs", () => { + it("RR1: records exactly the saved run, without allocating or resolving", function* () { const stream = new InMemoryStream(); - const attempt = yield* runRetained(RETAINED, stream); + const attempt = yield* runSaved(SAVED, stream); expect(attempt.thrown).toBeUndefined(); - expect(attempt.seen).toEqual([RETAINED]); + expect(attempt.seen).toEqual([SAVED]); expect(recordedRun(stream)).toEqual({ runId: "release-1.4", bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", @@ -347,24 +347,24 @@ describe("Tier RR — retained workflow runs", () => { expect(workflowEvents(stream)).toHaveLength(1); }); - it("RR2: restores the retained run from a truncated journal without recording again", function* () { + it("RR2: restores the saved run from a truncated journal without recording again", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const resumed = partial(first); - const attempt = yield* runRetained(RETAINED, resumed); + const attempt = yield* runSaved(SAVED, resumed); expect(attempt.thrown).toBeUndefined(); - expect(attempt.seen).toEqual([RETAINED]); + expect(attempt.seen).toEqual([SAVED]); expect(workflowEvents(resumed)).toHaveLength(1); }); - it("RR3: restores the retained run from a completed journal", function* () { + it("RR3: restores the saved run from a completed journal", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const replayed = new InMemoryStream(first.snapshot()); - const attempt = yield* runRetained(RETAINED, replayed); + const attempt = yield* runSaved(SAVED, replayed); expect(attempt.thrown).toBeUndefined(); expect(workflowEvents(replayed)).toHaveLength(1); @@ -372,9 +372,9 @@ describe("Tier RR — retained workflow runs", () => { it("RR4: refuses a journal recording a different run id", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); - const attempt = yield* runRetained({ ...RETAINED, runId: "release-1.5" }, partial(first)); + const attempt = yield* runSaved({ ...SAVED, runId: "release-1.5" }, partial(first)); expect(attempt.thrown).toBeInstanceOf(StaleInputError); const message = attempt.thrown instanceof Error ? attempt.thrown.message : ""; @@ -387,23 +387,20 @@ describe("Tier RR — retained workflow runs", () => { it("RR5: refuses a journal recording a different bundle or target", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); - const bundle = yield* runRetained( - { ...RETAINED, bundleHash: "ab".padEnd(64, "0") }, - partial(first), - ); + const bundle = yield* runSaved({ ...SAVED, bundleHash: "ab".padEnd(64, "0") }, partial(first)); expect(bundle.thrown).toBeInstanceOf(StaleInputError); expect(bundle.thrown instanceof Error ? bundle.thrown.message : "").toContain("bundleHash"); - const targeted = yield* runRetained({ ...RETAINED, targetPath: "Release" }, partial(first)); + const targeted = yield* runSaved({ ...SAVED, targetPath: "Release" }, partial(first)); expect(targeted.thrown).toBeInstanceOf(StaleInputError); expect(targeted.thrown instanceof Error ? targeted.thrown.message : "").toContain("targetPath"); }); it("RR6: refuses a record that does not describe a workflow run at all", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const damaged = new InMemoryStream( partial(first) @@ -415,7 +412,7 @@ describe("Tier RR — retained workflow runs", () => { ), ); - const attempt = yield* runRetained(RETAINED, damaged); + const attempt = yield* runSaved(SAVED, damaged); expect(attempt.thrown).toBeInstanceOf(StaleInputError); }); @@ -425,7 +422,7 @@ describe("Tier RR — retained workflow runs", () => { // would answer for history that never identified this run. it("RR8: refuses a completed journal that does not record exactly one run", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const cases: Array<{ says: string; stream: InMemoryStream }> = [ { says: "no successful workflow run", stream: completedWith(first, () => []) }, @@ -443,7 +440,7 @@ describe("Tier RR — retained workflow runs", () => { for (const refused of cases) { const before = refused.stream.snapshot().length; - const attempt = yield* runRetained(RETAINED, refused.stream); + const attempt = yield* runSaved(SAVED, refused.stream); expect(attempt.thrown).toBeInstanceOf(StaleInputError); expect(attempt.thrown instanceof Error ? attempt.thrown.message : "").toContain(refused.says); @@ -456,15 +453,15 @@ describe("Tier RR — retained workflow runs", () => { it("RR9: refuses a completed journal recording another run, or an unreadable one", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const completed = () => new InMemoryStream(first.snapshot()); - const foreign = yield* runRetained({ ...RETAINED, runId: "release-1.5" }, completed()); + const foreign = yield* runSaved({ ...SAVED, runId: "release-1.5" }, completed()); expect(foreign.thrown).toBeInstanceOf(StaleInputError); expect(foreign.thrown instanceof Error ? foreign.thrown.message : "").toContain("runId"); - const damaged = yield* runRetained( - RETAINED, + const damaged = yield* runSaved( + SAVED, completedWith(first, (event) => [ { ...event, result: { status: "ok", value: { runId: 7 } } }, ]), @@ -478,14 +475,14 @@ describe("Tier RR — retained workflow runs", () => { // recorded root result back to run-b. Identity is not policy, so it does not. it("RR10: refuses another run's completed journal beneath a suppressing guard", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); for (const phase of ["check", "admit", "decide"] as const) { const completed = new InMemoryStream(first.snapshot()); const before = completed.snapshot().length; - const attempt = yield* runRetained( - { ...RETAINED, runId: "release-1.5" }, + const attempt = yield* runSaved( + { ...SAVED, runId: "release-1.5" }, completed, useSuppressingGuard(phase), ); @@ -500,13 +497,13 @@ describe("Tier RR — retained workflow runs", () => { it("RR11: refuses it beneath a same-named guard this suite built itself", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const completed = new InMemoryStream(first.snapshot()); const before = completed.snapshot().length; - const attempt = yield* runRetained( - { ...RETAINED, runId: "release-1.5" }, + const attempt = yield* runSaved( + { ...SAVED, runId: "release-1.5" }, completed, useForeignGuard(), ); @@ -519,11 +516,11 @@ describe("Tier RR — retained workflow runs", () => { it("RR12: a valid completed journal still replays beneath a suppressing guard", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); for (const phase of ["check", "admit", "decide"] as const) { const completed = new InMemoryStream(first.snapshot()); - const attempt = yield* runRetained(RETAINED, completed, useSuppressingGuard(phase)); + const attempt = yield* runSaved(SAVED, completed, useSuppressingGuard(phase)); expect(attempt.thrown).toBeUndefined(); // Zero live execution: the recorded result answered. @@ -536,11 +533,11 @@ describe("Tier RR — retained workflow runs", () => { // let through, and it may still refuse — it simply cannot widen. it("RR13: a public guard still observes admitted history and may reject it", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const observed: string[] = []; - const watched = yield* runRetained( - RETAINED, + const watched = yield* runSaved( + SAVED, new InMemoryStream(first.snapshot()), ReplayGuard.around({ *check([event], next) { @@ -552,8 +549,8 @@ describe("Tier RR — retained workflow runs", () => { expect(watched.thrown).toBeUndefined(); expect(observed).toContain("workflow_run"); - const rejected = yield* runRetained( - RETAINED, + const rejected = yield* runSaved( + SAVED, new InMemoryStream(first.snapshot()), ReplayGuard.around({ // deno-lint-ignore require-yield @@ -573,7 +570,7 @@ describe("Tier RR — retained workflow runs", () => { // removing the genuine record and adding one of these would authorize reuse. it("RR14: refuses a record that is not the root coroutine's canonical one", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const counterfeits: Array<{ says: string; change: (event: Yield) => DurableEvent[] }> = [ { @@ -593,7 +590,7 @@ describe("Tier RR — retained workflow runs", () => { ...event, result: { status: "ok", - value: { ...RETAINED, executor: "someone-else" }, + value: { ...SAVED, executor: "someone-else" }, }, }, ], @@ -603,7 +600,7 @@ describe("Tier RR — retained workflow runs", () => { for (const counterfeit of counterfeits) { const damaged = completedWith(first, counterfeit.change); const before = damaged.snapshot().length; - const attempt = yield* runRetained(RETAINED, damaged); + const attempt = yield* runSaved(SAVED, damaged); expect(attempt.thrown).toBeInstanceOf(StaleInputError); expect(attempt.seen).toEqual([]); @@ -619,7 +616,7 @@ describe("Tier RR — retained workflow runs", () => { // establishment failure is not a licence to establish the run again. it("RR15: refuses a truncated journal whose run record failed", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const failed = new InMemoryStream( partial(first) @@ -632,7 +629,7 @@ describe("Tier RR — retained workflow runs", () => { ); const before = failed.snapshot().length; - const attempt = yield* runRetained(RETAINED, failed); + const attempt = yield* runSaved(SAVED, failed); expect(attempt.thrown).toBeInstanceOf(StaleInputError); expect(attempt.seen).toEqual([]); @@ -645,10 +642,10 @@ describe("Tier RR — retained workflow runs", () => { // RR16: nothing about the run reaches the error object, only the message. it("RR16: a refusal retains no run id, base, pinned commit or planted member", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); - const attempt = yield* runRetained( - { ...RETAINED, runId: "release-1.5" }, + const attempt = yield* runSaved( + { ...SAVED, runId: "release-1.5" }, new InMemoryStream(first.snapshot()), ); @@ -670,8 +667,8 @@ describe("Tier RR — retained workflow runs", () => { // built, after every handler has had its turn. it("RR17: refuses another run's journal however Execution middleware is ordered", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); - const other = { ...RETAINED, runId: "release-1.5" }; + yield* runSaved(SAVED, first); + const other = { ...SAVED, runId: "release-1.5" }; const hijacks: Array<{ says: string; install: (raw: DurableStream) => Operation }> = [ { says: "the package's own descriptor", install: useStreamHijack }, @@ -684,7 +681,7 @@ describe("Tier RR — retained workflow runs", () => { for (const order of ["before", "after"] as const) { const raw = new InMemoryStream(first.snapshot()); const handler = hijack.install(raw); - const attempt = yield* runRetained( + const attempt = yield* runSaved( other, new InMemoryStream(first.snapshot()), order === "before" ? handler : ok(), @@ -702,16 +699,16 @@ describe("Tier RR — retained workflow runs", () => { // RR18: admission and replay read one history, not two. The journal below // answers differently on a second read; what makes that harmless is that the - // history is retained once, before anything is decided, and every phase is + // history is saved once, before anything is decided, and every phase is // handed those same objects. - it("RR18: identity, guard observation and replay consume one retained snapshot", function* () { + it("RR18: identity, guard observation and replay consume one saved snapshot", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const reads: string[] = []; const observed: Json[] = []; - const attempt = yield* runRetained( - RETAINED, + const attempt = yield* runSaved( + SAVED, shiftingJournal(first.snapshot(), reads), ReplayGuard.around({ *check([event], next) { @@ -724,7 +721,7 @@ describe("Tier RR — retained workflow runs", () => { ); // The backend was asked once and never again, which is what makes the - // shift unreachable: the history is retained before anything is decided. + // shift unreachable: the history is saved before anything is decided. expect(reads).toEqual(["release-1.4"]); // Admission agreed with that settlement, so nothing was refused... expect(attempt.thrown).toBeUndefined(); @@ -742,7 +739,7 @@ describe("Tier RR — retained workflow runs", () => { // describe, not one to step past on the way to a record that does read. it("RR19: refuses a history holding an event that will not read", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const refusing: DurableEvent = { get type(): never { @@ -751,7 +748,7 @@ describe("Tier RR — retained workflow runs", () => { } as unknown as DurableEvent; const watched = unreadableJournal([refusing, ...first.snapshot()]); - const attempt = yield* runRetained(RETAINED, watched.journal); + const attempt = yield* runSaved(SAVED, watched.journal); expect(attempt.thrown).toBeInstanceOf(Error); expect(attempt.seen).toEqual([]); @@ -765,14 +762,14 @@ describe("Tier RR — retained workflow runs", () => { // the same fixed refusal, carrying none of its own text. it("RR20: a hostile recorded value becomes the fixed refusal and leaks nothing", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const hostile: Array<{ says: string; value: () => unknown }> = [ { says: "ownKeys refuses", value: () => new Proxy( - { ...RETAINED }, + { ...SAVED }, { ownKeys() { throw new Error("PLANTED-OWNKEYS"); @@ -784,7 +781,7 @@ describe("Tier RR — retained workflow runs", () => { says: "getOwnPropertyDescriptor refuses", value: () => new Proxy( - { ...RETAINED }, + { ...SAVED }, { getOwnPropertyDescriptor() { throw new Error("PLANTED-DESCRIPTOR"); @@ -804,7 +801,7 @@ describe("Tier RR — retained workflow runs", () => { { says: "classification refuses", value: () => { - const revoked = Proxy.revocable({ ...RETAINED }, {}); + const revoked = Proxy.revocable({ ...SAVED }, {}); revoked.revoke(); return revoked.proxy; }, @@ -822,7 +819,7 @@ describe("Tier RR — retained workflow runs", () => { ), ); - const attempt = yield* runRetained(RETAINED, watched.journal); + const attempt = yield* runSaved(SAVED, watched.journal); expect(attempt.thrown).toBeInstanceOf(StaleInputError); // No terminal result reused, no document code, no output, no append. @@ -848,7 +845,7 @@ describe("Tier RR — retained workflow runs", () => { // ended. A failure beside a success is two runs, not one run with a stumble. it("RR21: refuses a duplicate canonical record in every settlement combination", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const failure = (event: Yield): DurableEvent => ({ ...event, result: { status: "err", error: { message: "planted-second-establishment" } }, @@ -867,7 +864,7 @@ describe("Tier RR — retained workflow runs", () => { for (const combination of combinations) { const journal = completedWith(first, combination.change); const before = journal.snapshot().length; - const attempt = yield* runRetained(RETAINED, journal); + const attempt = yield* runSaved(SAVED, journal); expect(attempt.thrown).toBeInstanceOf(StaleInputError); expect(attempt.thrown instanceof Error ? attempt.thrown.message : "").toContain("2"); @@ -879,10 +876,10 @@ describe("Tier RR — retained workflow runs", () => { }); // RR22: the same totality on the other side of the boundary. A host hands the - // retained run over directly, so its members are read here rather than out of + // saved run over directly, so its members are read here rather than out of // a journal — and a member that refuses is a value identifying no run, which // is a sentence rather than the host's own exception. - it("RR22: refuses a retained run whose members refuse to be read", function* () { + it("RR22: refuses a saved run whose members refuse to be read", function* () { const hostile = { bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", get runId(): never { @@ -892,7 +889,7 @@ describe("Tier RR — retained workflow runs", () => { const thrown = yield* scoped(function* () { try { - const workflow = retainedWorkflowInstallation(hostile as unknown as WorkflowRun); + const workflow = savedWorkflowInstallation(hostile as unknown as WorkflowRun); return undefined; } catch (error) { return error; @@ -910,19 +907,19 @@ describe("Tier RR — retained workflow runs", () => { // something this version cannot account for. it("RR23: a definition target is not part of run identity", function* () { const first = new InMemoryStream(); - yield* runRetained(RETAINED, first); + yield* runSaved(SAVED, first); const withTarget = completedWith(first, (event) => [ { ...event, result: { status: "ok", - value: { ...RETAINED, targetPath: "Release/Publish" }, + value: { ...SAVED, targetPath: "Release/Publish" }, }, }, ]); const before = withTarget.snapshot().length; - const attempt = yield* runRetained(RETAINED, withTarget); + const attempt = yield* runSaved(SAVED, withTarget); expect(attempt.thrown).toBeInstanceOf(StaleInputError); expect(attempt.seen).toEqual([]); @@ -935,7 +932,7 @@ describe("Tier RR — retained workflow runs", () => { it("RR7: refuses to install a run that identifies nothing", function* () { const empty = yield* scoped(function* () { try { - const workflow = retainedWorkflowInstallation({ + const workflow = savedWorkflowInstallation({ runId: "", bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", }); @@ -948,7 +945,7 @@ describe("Tier RR — retained workflow runs", () => { const unpinned = yield* scoped(function* () { try { - const workflow = retainedWorkflowInstallation({ runId: "r", bundleHash: "" }); + const workflow = savedWorkflowInstallation({ runId: "r", bundleHash: "" }); return undefined; } catch (error) { return error; diff --git a/packages/workflow/tests/support/artifact-fixture.ts b/packages/workflow/tests/support/artifact-fixture.ts index 7a78bd1fb..6e7ba939c 100644 --- a/packages/workflow/tests/support/artifact-fixture.ts +++ b/packages/workflow/tests/support/artifact-fixture.ts @@ -12,7 +12,7 @@ * Workspace roots that share some content and not the rest, a hardlink group, a * symbolic link, a non-default file mode, sourced and inherited journal rows, a * prior fork lineage, both suspension-answer states, a Repository with a - * Worktree, an Agent session mapping, a retained external effect, and one + * Worktree, an Agent session mapping, a saved external effect, and one * declared component the run never expanded. */ @@ -59,7 +59,7 @@ export const SOURCE_RUN_ID = "release-1.3"; * fixture cannot drift from the shape the verifier authenticates against. The * request carries the wait's identity twice over and the schema an answer is * judged by; the publication is what makes an answer consumed rather than - * pending, and carries the value that was retained. + * pending, and carries the value that was saved. */ export function wait( id: string, @@ -216,7 +216,7 @@ const CHECKLIST = "- [ ] tag\n"; const UNUSED = "This component was declared and never expanded.\n"; /** Every path this fixture's bundle retains, in canonical order. */ -const RETAINED: readonly { readonly path: string; readonly content: string }[] = [ +const SAVED: readonly { readonly path: string; readonly content: string }[] = [ { path: "workflows/Checklist.md", content: CHECKLIST }, { path: "workflows/Unused.md", content: UNUSED }, { path: "workflows/release.md", content: ROOT_DOCUMENT }, @@ -240,7 +240,7 @@ export function definitionOf(overrides: Record = {}): WorkflowD hashAlgorithm: "sha256", bundleHash: FIXTURE_BUNDLE_HASH, entrypoint: "workflows/release.md", - sources: RETAINED.map((entry) => ({ + sources: SAVED.map((entry) => ({ path: entry.path, sourceHash: SOURCE_HASHES[entry.path], byteLength: new TextEncoder().encode(entry.content).byteLength, @@ -258,15 +258,15 @@ export function definitionOf(overrides: Record = {}): WorkflowD return parsed.value; } -/** The retained closure those sources are, as the store hands it back. */ -export function retainedSourcesOf(): { +/** The saved closure those sources are, as the store hands it back. */ +export function savedSourcesOf(): { definition: WorkflowDefinition; sources: readonly { path: string; bytes: Uint8Array }[]; } { const encoder = new TextEncoder(); return { definition: definitionOf(), - sources: RETAINED.map((entry) => ({ + sources: SAVED.map((entry) => ({ path: entry.path, bytes: encoder.encode(entry.content), })), @@ -285,7 +285,7 @@ export const SUSPENSIONS = { /** * A wait the *source* run answered, inherited whole. * - * Its request and its publication are both copied by a fork; the retained + * Its request and its publication are both copied by a fork; the saved * answer row behind them is not, because fork creation copies journal rows, * Workspace roots and checkout records and nothing else. So this pair is * present with no row of its own — which is a shape the verifier has to @@ -296,7 +296,7 @@ export const SUSPENSIONS = { { kind: "approval", release: "1.3" }, { type: "object", properties: { approved: { type: "boolean" } }, required: ["approved"] }, ), - /** A wait this run reached and answered itself. Its row is retained. */ + /** A wait this run reached and answered itself. Its row is saved. */ consumed: wait( suspensionId(RUN_ID, { coroutineId: "root", index: 3 }), { kind: "approval", release: "1.4" }, @@ -374,7 +374,7 @@ export function richArtifact(): DetachedXmdArtifact { own: false, }, { - // root.0/0 — a retained external effect keeps its own journal record and + // root.0/0 — a saved external effect keeps its own journal record and // nothing else: the record is the evidence, not a second projection. event: sourced( { @@ -410,7 +410,7 @@ export function richArtifact(): DetachedXmdArtifact { record: serializeDurableEvent(each.event), workspaceRootId: each.root, // Provenance marks exactly the rows a fork copies. A row this run wrote - // carries none, and its absence is what says so — so a retained answer row, + // carries none, and its absence is what says so — so a saved answer row, // which fork creation never copies, never sits behind an inherited request. ...(each.own ? {} @@ -503,11 +503,11 @@ export function richArtifact(): DetachedXmdArtifact { }, ], agentSessions: [agentSession()], - definition: retainedSourcesOf(), + definition: savedSourcesOf(), }; } -/** One Agent session mapping, retained under the key its identity derives. */ +/** One Agent session mapping, saved under the key its identity derives. */ function agentSession( sessionIdentity = "expansion:release/Agent[0]/Session[0]", assertion = "sess_01J", @@ -588,7 +588,7 @@ export function agentBundle(): Uint8Array { } /** - * One retained Prompt, as the durable event a live run journals for it. + * One saved Prompt, as the durable event a live run journals for it. * * The result is the serialized Prompt record the Agent package's own parser * reads, so the fixture cannot drift from the shape the artifact verifier @@ -623,13 +623,13 @@ function promptEvent( } /** - * The same snapshot, finalized: every session that retained a Prompt is + * The same snapshot, finalized: every session that saved a Prompt is * classified. * * The Prompts run in a coroutine of their own, appended after everything the * rich snapshot already held, so the durable positions the suspension * identities are derived from cannot move. One session is portable with two - * ordered associations and a bundle; one lacks retained evidence because a + * ordered associations and a bundle; one lacks saved evidence because a * Prompt failed; one was complete and captured by nothing. The mapping the rich * snapshot already carried retains no Prompt at all, so it is classified by * nothing — which is the shape a finalized artifact has to accept. @@ -725,7 +725,7 @@ export const FIXTURE_BUNDLE_PATH = "release.md"; export const FIXTURE_BUNDLE_SOURCE = "# Release\n\nthe bytes this artifact seals\n"; /** - * The same snapshot, as a run of a retained source bundle. + * The same snapshot, as a run of a saved source bundle. * * Everything outside the definition is the version-1 fixture's, so what a * format-2 case is comparing is the definition and nothing else. The descriptor diff --git a/packages/workflow/tests/support/storage.ts b/packages/workflow/tests/support/storage.ts index 07bd5de4f..9867a0998 100644 --- a/packages/workflow/tests/support/storage.ts +++ b/packages/workflow/tests/support/storage.ts @@ -390,7 +390,7 @@ export function* sourceBundleCreation( * recompute every hash from the snapshot they are given, so a fixture naming * anything else retains nothing. */ -export function* retainedSource( +export function* savedSource( entrypoint: string, content: string, ): Operation<{ @@ -418,7 +418,7 @@ export function* retainedSource( } /** - * A creation over several retained sources, with a declared component mapping. + * A creation over several saved sources, with a declared component mapping. * * The manifest is derived from the bytes and sorted canonically, so a fixture * says what it retains and the descriptor it gets is the one those bytes diff --git a/packages/workflow/tests/support/workflow-recovery-child.ts b/packages/workflow/tests/support/workflow-recovery-child.ts index 2ba91f1bb..168abc3ed 100644 --- a/packages/workflow/tests/support/workflow-recovery-child.ts +++ b/packages/workflow/tests/support/workflow-recovery-child.ts @@ -57,7 +57,7 @@ function hot(path: string): void { .get(); const root = anchor?.["root"]; if (typeof root !== "string") { - throw new Error("the run has no retained event to anchor uncommitted rows to"); + throw new Error("the run has no saved event to anchor uncommitted rows to"); } database.exec("BEGIN IMMEDIATE"); diff --git a/packages/workflow/tests/support/workspace-crash-child.ts b/packages/workflow/tests/support/workspace-crash-child.ts index 53d2ba60f..022863fae 100644 --- a/packages/workflow/tests/support/workspace-crash-child.ts +++ b/packages/workflow/tests/support/workspace-crash-child.ts @@ -104,10 +104,10 @@ function* crash(root: string, runId: string): Operation { ready: true, content: yield* live.readTextFile(CRASH_PATH), currentRoot, - retainedRoots: count( + savedRoots: count( sqlite.prepare("SELECT COUNT(*) AS count FROM workspace_roots").get()?.["count"], ), - currentRootRetained: count( + currentRootSaved: count( sqlite .prepare("SELECT COUNT(*) AS count FROM workspace_roots WHERE root_id = ?") .get(currentRoot)?.["count"], diff --git a/packages/workflow/tests/workflow-agent-checkpoints.test.ts b/packages/workflow/tests/workflow-agent-checkpoints.test.ts index 4fa7c03f3..2fc45ef8b 100644 --- a/packages/workflow/tests/workflow-agent-checkpoints.test.ts +++ b/packages/workflow/tests/workflow-agent-checkpoints.test.ts @@ -2,7 +2,7 @@ * Tier WPC — which provider turn each of a run's Prompts was * (specs/workflow-workspace-spec.md §8.6). * - * A retained Agent session says which conversation this run is having. A + * A saved Agent session says which conversation this run is having. A * checkpoint says where in that conversation one Prompt landed. These tests * verify that the second fact commits with the Prompt it describes and with * nothing else — so these drive the real publisher, the real transaction and @@ -66,7 +66,7 @@ const IDENTITY: AgentSessionIdentity = { }; /** What this run retains the conversation under. */ -const RETAINED = agentSessionKey(IDENTITY); +const SAVED = agentSessionKey(IDENTITY); /** * What the provider calls the same session. @@ -75,7 +75,7 @@ const RETAINED = agentSessionKey(IDENTITY); * placement key does. Nothing under test may recover one from the other by * taking the spelling apart. */ -const PLACEMENT = `${RETAINED}:0123456789abcdef`; +const PLACEMENT = `${SAVED}:0123456789abcdef`; /** A session the run never placed, for the Prompt that must retain nothing. */ const FOREIGN_PLACEMENT = "some-other-provider-key"; @@ -183,7 +183,7 @@ function scriptedProvider(replies: readonly Reply[]): AgentProviderFactory { function* retainSession(database: WorkflowRunDatabase): Operation { const committed = yield* transactAgentSessions(database, function* (sessions) { sessions.commit({ - sessionKey: RETAINED, + sessionKey: SAVED, ...IDENTITY, policy: POLICY, assertion: { kind: "acpx.agentSessionId", value: "agent-session:alpha" }, @@ -218,7 +218,7 @@ function runDocument( yield* useAgentPromptPublisher( createWorkflowPromptPublisher({ database, - retainedSessionKey: (key) => (placed.includes(key) ? RETAINED : undefined), + savedSessionKey: (key) => (placed.includes(key) ? SAVED : undefined), }), ); const factory = scriptedProvider(replies); @@ -253,7 +253,7 @@ function runDocument( }); } -/** Every `agent_prompt` event this run retained, in journal order. */ +/** Every `agent_prompt` event this run saved, in journal order. */ function prompts(events: readonly DurableEvent[]): DurableEvent[] { return events.filter( (event) => event.type === "yield" && event.description.type === "agent_prompt", @@ -416,7 +416,7 @@ const THREE_PROMPTS = `# Review `; -describe("Tier WPC — retained Prompt checkpoints", () => { +describe("Tier WPC — saved Prompt checkpoints", () => { it("WPC1: one commit holds the Prompt, its Workspace root, the session and the token", function* () { const root = yield* useStorageRoot(); const path = runPath(root, "release-1.4"); @@ -442,7 +442,7 @@ describe("Tier WPC — retained Prompt checkpoints", () => { expect(yield* associations(database)).toEqual([ { eventId: event!.eventId, - sessionKey: RETAINED, + sessionKey: SAVED, provider: "codex", tokenKind: "app-server-turn-id", tokenValue: "turn-alpha", @@ -451,7 +451,7 @@ describe("Tier WPC — retained Prompt checkpoints", () => { }); }); - it("WPC2: the retained session key is the run's, not the provider's", function* () { + it("WPC2: the saved session key is the run's, not the provider's", function* () { const root = yield* useStorageRoot(); const path = runPath(root, "release-1.4"); yield* withStorage(root, function* () { @@ -464,7 +464,7 @@ describe("Tier WPC — retained Prompt checkpoints", () => { // The two keys share a prefix and are different things. A row carrying // the provider's key would satisfy every shape check here and reference // a session this run does not retain. - expect(row?.["session_key"]).toBe(RETAINED); + expect(row?.["session_key"]).toBe(SAVED); expect(row?.["session_key"]).not.toBe(PLACEMENT); }); }); @@ -608,7 +608,7 @@ describe("Tier WPC — retained Prompt checkpoints", () => { yield* retainSession(database); // The first attempt is interrupted inside its third turn, so two Prompts - // are retained and the third was never journaled at all — which is the + // are saved and the third was never journaled at all — which is the // history a continuation actually meets. const reached = withResolvers(); const attempt = yield* spawn(() => @@ -698,7 +698,7 @@ describe("Tier WPC — retained Prompt checkpoints", () => { yield* withStorage(root, function* () { yield* createRun(); }); - // A legacy run that retained no session either, so the association's + // A legacy run that saved no session either, so the association's // foreign key is what fails. The fault comes from the table's own // constraint rather than from anything added to the schema to cause it. makeLegacy(path); diff --git a/packages/workflow/tests/workflow-agent-sessions.test.ts b/packages/workflow/tests/workflow-agent-sessions.test.ts index 24203b350..5679b7f47 100644 --- a/packages/workflow/tests/workflow-agent-sessions.test.ts +++ b/packages/workflow/tests/workflow-agent-sessions.test.ts @@ -79,13 +79,13 @@ function record(overrides: Partial = {}): AgentSessionRecord /** What one resolution did, as a value rather than a throw. */ function attempt( - retained: AgentSessionRecord | undefined, + saved: AgentSessionRecord | undefined, policy: string, assertions: readonly ProviderAssertion[], who: AgentSessionIdentity, ): { kind?: string; refusal?: string } { try { - return { kind: resolveAgentSession(retained, policy, assertions, who).kind }; + return { kind: resolveAgentSession(saved, policy, assertions, who).kind }; } catch (error) { return { refusal: error instanceof Error ? error.message : String(error) }; } @@ -126,7 +126,7 @@ function withLifecycle(root: string, body: () => Operation): Operation }); } -describe("Tier WSL — retained workflow Agent sessions", () => { +describe("Tier WSL — saved workflow Agent sessions", () => { it("WSL1: the mapping key is the engine identity alone", function* () { // Two sibling `` elements: same authored name, same // provider, same agent, different engine identity. Two sessions. @@ -164,7 +164,7 @@ describe("Tier WSL — retained workflow Agent sessions", () => { it("WSL4: missing, mismatched, replaced, changed and ambiguous each refuse", function* () { const cases = [ { - name: "the provider asserts nothing for a session this run retained", + name: "the provider asserts nothing for a session this run saved", assertions: [] as ProviderAssertion[], who: identity(), says: "asserts no durable identity", @@ -220,16 +220,16 @@ describe("Tier WSL — retained workflow Agent sessions", () => { } }); - it("WSL5: a matching assertion reattaches the retained mapping unchanged", function* () { - const retained = record(); + it("WSL5: a matching assertion reattaches the saved mapping unchanged", function* () { + const saved = record(); const outcome = resolveAgentSession( - retained, + saved, POLICY, [asserted("agent-session:alpha")], identity(), ); expect(outcome.kind).toBe("reattach"); - expect(outcome.kind === "reattach" ? outcome.record : undefined).toBe(retained); + expect(outcome.kind === "reattach" ? outcome.record : undefined).toBe(saved); }); it("WSL6: the mapping commits in the run's own transaction and reads back per Session", function* () { @@ -267,7 +267,7 @@ describe("Tier WSL — retained workflow Agent sessions", () => { // Rolled back with everything else the attempt did: an interruption // between the provider's assertion and this run recording it leaves - // nothing retained, which is the state the pre-commit rule reconciles. + // nothing saved, which is the state the pre-commit rule reconciles. expect(yield* readMapping(database, key)).toBe(undefined); }); }); diff --git a/packages/workflow/tests/workflow-bundle.test.ts b/packages/workflow/tests/workflow-bundle.test.ts index 3fbef5b6c..7cfcbe624 100644 --- a/packages/workflow/tests/workflow-bundle.test.ts +++ b/packages/workflow/tests/workflow-bundle.test.ts @@ -1,13 +1,13 @@ /** - * Tier WBA — holding a retained history to the component bundle. + * Tier WBA — holding a saved history to the component bundle. * * `workflowBundleInstallation()` carries two halves of one fact. Live import - * resolves a declared name to its pinned source, which core owns. Retained + * resolves a declared name to its pinned source, which core owns. Saved * history is held to the same components *before* any of it is replayed from, * which is what this suite drives. * * The admission runs inside canonical core's own journal read: ahead of public - * replay policy, of any retained effect reaching execution, of a retained + * replay policy, of any saved effect reaching execution, of a saved * terminal result being reused, and of every append. So a refusal here leaves * the journal exactly as it was and invokes nothing — which is what each case * below asserts alongside the refusal itself. @@ -22,7 +22,7 @@ import { expect } from "@executablemd/test-support/expect"; import type { Operation } from "effection"; import { InMemoryStream } from "@executablemd/durable-streams"; import type { DurableEvent, DurableStream, Json } from "@executablemd/durable-streams"; -import { collect, retainedSource } from "@executablemd/core"; +import { collect, savedSource } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import type { ExecutionInstallation, WorkflowBundleComponent } from "@executablemd/core/host"; import { workflowBundleInstallation } from "../mod.ts"; @@ -57,7 +57,7 @@ function* run( extra: readonly ExecutionInstallation[] = [], ): Operation { return yield* collect( - yield* executeInstalled({ ...retainedSource(ROOT_PATH, source), stream, includes: [] }, [ + yield* executeInstalled({ ...savedSource(ROOT_PATH, source), stream, includes: [] }, [ workflowBundleInstallation(components), ...extra, ]), @@ -102,9 +102,9 @@ function planting(value: unknown): (event: DurableEvent) => DurableEvent { /** What a refused execution reported, and what it did to the journal. */ function* refused( stream: DurableStream, - retained?: number, + saved?: number, ): Operation<{ message: string; appended: number }> { - const before = retained ?? (yield* stream.readAll()).length; + const before = saved ?? (yield* stream.readAll()).length; try { yield* run(ROOT, stream); } catch (error) { @@ -113,10 +113,10 @@ function* refused( appended: (yield* stream.readAll()).length - before, }; } - throw new Error("expected the retained history to be refused"); + throw new Error("expected the saved history to be refused"); } -describe("Tier WBA — a retained history is held to the bundle", () => { +describe("Tier WBA — a saved history is held to the bundle", () => { it("WBA1: a history recording exactly this bundle replays", function* () { const partial = yield* continuing(ROOT); const before = (yield* partial.readAll()).filter((event) => isImport(event)).length; @@ -140,8 +140,7 @@ describe("Tier WBA — a retained history is held to the bundle", () => { const outcome = yield* refused(planted); expect({ says, message: outcome.message, appended: outcome.appended }).toEqual({ says, - message: - "A retained component import does not match the component this run's bundle holds.", + message: "A saved component import does not match the component this run's bundle holds.", appended: 0, }); } @@ -157,7 +156,7 @@ describe("Tier WBA — a retained history is held to the bundle", () => { : event, ); expect((yield* refused(renamed)).message).toBe( - "A retained component import names a component this run's bundle does not declare.", + "A saved component import names a component this run's bundle does not declare.", ); }); @@ -189,7 +188,7 @@ describe("Tier WBA — a retained history is held to the bundle", () => { ); expect((yield* refused(planted)).message).toBe( - "A retained component import recorded a repository file, which a workflow run resolves " + + "A saved component import recorded a repository file, which a workflow run resolves " + "none of.", ); }); @@ -272,7 +271,7 @@ describe("Tier WBA — a retained history is held to the bundle", () => { ); expect((yield* refused(planted)).message).toBe( - "A retained component import names a component this run's bundle does not declare.", + "A saved component import names a component this run's bundle does not declare.", ); }); diff --git a/packages/workflow/tests/workflow-checkpoint.test.ts b/packages/workflow/tests/workflow-checkpoint.test.ts index b151a296c..af5d05c24 100644 --- a/packages/workflow/tests/workflow-checkpoint.test.ts +++ b/packages/workflow/tests/workflow-checkpoint.test.ts @@ -105,19 +105,19 @@ function withRun( }); } -/** Every durable yield this run retained, as `type` alone. */ +/** Every durable yield this run saved, as `type` alone. */ function* yields(database: WorkflowRunDatabase): Operation { const events: DurableEvent[] = yield* database.journal.readAll(); return events.filter(isYield).map((event) => event.description.type); } -/** The retained suspension requests, in order. */ +/** The saved suspension requests, in order. */ function* requests(database: WorkflowRunDatabase): Operation { const events: DurableEvent[] = yield* database.journal.readAll(); return events.filter(isYield).filter((event) => event.description.type === SUSPENSION_REQUEST); } -/** The run's retained status, read the way something outside XMD would. */ +/** The run's saved status, read the way something outside XMD would. */ function status(root: string, runId: string): string { const database = new DatabaseSync(runPath(root, runId), { readOnly: true }); try { @@ -129,7 +129,7 @@ function status(root: string, runId: string): string { } } -/** One retained JSON object field, parsed rather than asserted. */ +/** One saved JSON object field, parsed rather than asserted. */ function object(value: Json | undefined, what: string): Record { if (!isJsonObject(value)) { throw new Error(`${what} is not a JSON object`); @@ -189,12 +189,10 @@ describe("Tier CK — the durable checkpoint", () => { // What the person was asked, and what an answer must satisfy, are the // request — not something a later execution reconstructs. const description = published[0].description; - const request = object(description.request, "the retained request"); + const request = object(description.request, "the saved request"); expect(request.kind).toBe("elicitation"); expect(String(request.message)).toContain("Proceed with the change?"); - expect(object(description.responseSchema, "the retained response schema").type).toBe( - "object", - ); + expect(object(description.responseSchema, "the saved response schema").type).toBe("object"); expect(yield* yields(database)).not.toContain(ELICIT); }); diff --git a/packages/workflow/tests/workflow-definition.test.ts b/packages/workflow/tests/workflow-definition.test.ts index 890841d4b..e4a3f4788 100644 --- a/packages/workflow/tests/workflow-definition.test.ts +++ b/packages/workflow/tests/workflow-definition.test.ts @@ -7,7 +7,7 @@ * * Two properties are checked throughout. A shape is closed, so a member nobody * declared is a parse failure rather than a field silently dropped. And a - * failure never quotes the value it refused, because these values are retained + * failure never quotes the value it refused, because these values are saved * history and an error travels to logs and terminals. */ @@ -45,7 +45,7 @@ const SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 /** The root this suite's representative descriptor is a run of. */ const WD_ROOT = "workflows/release.md"; -/** A distinct source hash per retained path, so a swap is visible. */ +/** A distinct source hash per saved path, so a swap is visible. */ function digest(nth: number): string { return `${nth}`.repeat(2).padEnd(64, "0"); } @@ -60,7 +60,7 @@ const BUNDLE = [ ]; /** - * Every retained source, canonical: one entry per path, in the UTF-8 byte order + * Every saved source, canonical: one entry per path, in the UTF-8 byte order * of those paths, with the root among them. */ const WD_SOURCES = [ @@ -255,7 +255,7 @@ describe("Tier WD — compatible reuse", () => { "props", ]); - // A run is its retained bytes, so every way of being a run of something + // A run is its saved bytes, so every way of being a run of something // else is a different descriptor rather than a different repository state. for (const changed of [ parsed({ bundleHash: SHA256.replace("e", "a") }), @@ -297,7 +297,7 @@ describe("Tier WD — compatible reuse", () => { * Every target form this suite exercises, and whether a descriptor may carry it. * * Canonical encoding escapes everything outside RFC 3986's unreserved set, so - * a heading holding `/`, `*`, `#`, `%`, or a space is retained as an escape and + * a heading holding `/`, `*`, `#`, `%`, or a space is saved as an escape and * cannot be read back as hierarchy or operator syntax. */ const CANONICAL_TARGETS = [ @@ -433,7 +433,7 @@ describe("Tier WD — a definition's exact document target", () => { * * The bundle is a member of the one descriptor rather than a version past it. * Absent, it identifies a run closed over no components — which is what every - * definition retained before bundles existed is. Present, it is the exact set + * definition saved before bundles existed is. Present, it is the exact set * the root may resolve, and it is identity: canonical, one entry per name, * sorted by name, each holding the blob's own object id under the descriptor's * own format. @@ -1036,7 +1036,7 @@ describe("Tier WD — the snapshot a source bundle is created from", () => { expect(accepted.ok).toBe(true); }); - it("WD52: a retained source reads as UTF-8 strictly, and without normalization", function* () { + it("WD52: a saved source reads as UTF-8 strictly, and without normalization", function* () { const text = decodeSourceText(EMOJI_BYTES); expect(text.ok && text.value).toBe(EMOJI_TEXT); expect(decodeSourceText(LIGATURE_BYTES).ok).toBe(true); diff --git a/packages/workflow/tests/workflow-export.test.ts b/packages/workflow/tests/workflow-export.test.ts index 4703d49f0..f235fdd27 100644 --- a/packages/workflow/tests/workflow-export.test.ts +++ b/packages/workflow/tests/workflow-export.test.ts @@ -192,7 +192,7 @@ describe("exporting a workflow run", () => { * compared against the run's own live filesystem, walked path by path. The * definition is compared against the objects Git holds. The Repository and * Worktree rows are compared against the remote the run cloned and the commit - * its own history records making. The retained history is compared against the + * its own history records making. The saved history is compared against the * journal the run committed. * * The awkward cases are deliberate: several Workspace roots rather than one, a @@ -283,7 +283,7 @@ interface WorkspaceNode { * Every path the run's live Workspace holds, read through its own filesystem. * * The independent side of the Workspace comparison. This walks DOFS directly — - * `lstat`, `readlink`, `readFile` — rather than reading the retained root + * `lstat`, `readlink`, `readFile` — rather than reading the saved root * manifests, which is the reader an export already uses: comparing a manifest * against itself would agree however wrong both were. */ @@ -327,7 +327,7 @@ function base64(content: Uint8Array): string { return Buffer.from(content).toString("base64"); } -/** The same description, taken from one retained Workspace root of an artifact. */ +/** The same description, taken from one saved Workspace root of an artifact. */ function artifactWorkspace( artifact: VerifiedXmdArtifact, rootId: string, @@ -356,7 +356,7 @@ function artifactWorkspace( return { nodes, entries }; } -/** One retained effect's durable description type, in journal order. */ +/** One saved effect's durable description type, in journal order. */ function effectTypes(events: readonly DurableEvent[]): string[] { return events .filter((event) => event.type === "yield") @@ -364,8 +364,8 @@ function effectTypes(events: readonly DurableEvent[]): string[] { .filter((type) => type.startsWith("workspace_")); } -/** What the run's own history says one effect retained. */ -function retainedRecord(events: readonly DurableEvent[], type: string): Record { +/** What the run's own history says one effect saved. */ +function savedRecord(events: readonly DurableEvent[], type: string): Record { const event = events.find((entry) => entry.type === "yield" && entry.description.type === type); if (event === undefined || event.result.status !== "ok") { throw new Error(`the run journaled no successful ${type}`); @@ -374,7 +374,7 @@ function retainedRecord(events: readonly DurableEvent[], type: string): Record { - it("XE7 seals every retained family of one representative run", function* () { + it("XE7 seals every saved family of one representative run", function* () { const store = yield* useStorageRoot(); // A remote with the entries a summary would lose: a committed symbolic // link and a file whose executable bit is content as far as Git is @@ -537,15 +537,15 @@ describe("exporting a run that really ran", () => { requestedBranch: "review/1", requestedBase: null, // The commit this run's own history records making, a moment earlier. - creationCommit: retainedRecord(history, "workspace_git_commit")["commit"], + creationCommit: savedRecord(history, "workspace_git_commit")["commit"], }); expect(artifact.worktrees[0]?.checkoutPath).toBe( - retainedRecord(history, "workspace_worktree")["checkoutPath"], + savedRecord(history, "workspace_worktree")["checkoutPath"], ); const decode = (path: string): string | undefined => { - const retained = artifact.definition.sources.find((each) => each.path === path); - return retained === undefined ? undefined : new TextDecoder().decode(retained.bytes); + const saved = artifact.definition.sources.find((each) => each.path === path); + return saved === undefined ? undefined : new TextDecoder().decode(saved.bytes); }; expect(artifact.definition.definition.entrypoint).toBe(repository.root.path); diff --git a/packages/workflow/tests/workflow-fork-source.test.ts b/packages/workflow/tests/workflow-fork-source.test.ts index 0e579b5e9..df88c7d6e 100644 --- a/packages/workflow/tests/workflow-fork-source.test.ts +++ b/packages/workflow/tests/workflow-fork-source.test.ts @@ -1,5 +1,5 @@ /** - * Tier WFK — admitting a fork of a retained source bundle. + * Tier WFK — admitting a fork of a saved source bundle. * * Deno-only, and deliberately its own file: every case here takes the run's * executor lock, which `packages/workflow/src/deno/advisory-lock.ts` reaches @@ -42,8 +42,8 @@ import { DatabaseSync } from "node:sqlite"; import { workflowForkStaging } from "../deno.ts"; import { readRepositories, readWorktrees } from "../src/deno/fork-source.ts"; -/** One retained yield, as the journal holds it. */ -function retained(type: string, name = type, result: Json = null): DurableEvent { +/** One saved yield, as the journal holds it. */ +function saved(type: string, name = type, result: Json = null): DurableEvent { return { type: "yield", coroutineId: "root", @@ -76,7 +76,7 @@ describe("Tier WFK — a source-bundle fork", () => { }), ); yield* begun.database.journal.append(rootImport); - yield* begun.database.journal.append(retained("checkpoint")); + yield* begun.database.journal.append(saved("checkpoint")); const entries = yield* begun.database.readJournalEntries(); if (!entries.ok) { @@ -84,7 +84,7 @@ describe("Tier WFK — a source-bundle fork", () => { } const last = entries.value.at(-1); if (last === undefined) { - throw new Error("the source run retained no checkpoint"); + throw new Error("the source run saved no checkpoint"); } const settled = yield* transitions.settle(executorLock, { executionId: begun.execution.executionId, @@ -186,7 +186,7 @@ describe("Tier WFK — a source-bundle fork", () => { // What it assembled, read out of the staging file while the resource that // owns it is still alive. The kind alone would be satisfied by a staging - // copy that retained some other document; the bytes are what a + // copy that saved some other document; the bytes are what a // compatibility replay would actually run. const database = new DatabaseSync(workflowForkStaging(root, "fork-staged"), { readOnly: true, @@ -226,7 +226,7 @@ describe("Tier WFK — a source-bundle fork", () => { * this copy path would refuse these rows, and a column dropped or rewritten on * the way through would come back different. */ -describe("Tier WFO — retained checkout rows travel as stored", () => { +describe("Tier WFO — saved checkout rows travel as stored", () => { /** Values no Git parser would accept, in columns the physical schema allows. */ const REPOSITORY = Object.freeze({ name: "kept", diff --git a/packages/workflow/tests/workflow-fork.test.ts b/packages/workflow/tests/workflow-fork.test.ts index fdb9b5489..5e99bdb34 100644 --- a/packages/workflow/tests/workflow-fork.test.ts +++ b/packages/workflow/tests/workflow-fork.test.ts @@ -1,9 +1,9 @@ /** * Tier WFK — what a fork may inherit, decided before anything exists. * - * Two questions, both answered from retained values alone: whether a checkpoint + * Two questions, both answered from saved values alone: whether a checkpoint * can be forked at all, and which events a fork that selects it takes with it. - * Neither opens a database, so both are exercised here against retained shapes + * Neither opens a database, so both are exercised here against saved shapes * a run could hold rather than against a run that had to be produced. * * Nothing here opens a database, so this file runs under every runtime. The @@ -33,7 +33,7 @@ import type { ForkCandidate } from "@executablemd/workflow"; const ROOT_A = "a".repeat(64); const ROOT_B = "b".repeat(64); -function retained(type: string, name = type, result: Json = null): DurableEvent { +function saved(type: string, name = type, result: Json = null): DurableEvent { return { type: "yield", coroutineId: "root", @@ -49,7 +49,7 @@ function closed(coroutineId: string): DurableEvent { /** One history, classified against the roots the run still holds. */ function classify( events: readonly { id: string; event: DurableEvent; root?: string }[], - retainedRoots: readonly string[] = [ROOT_A, ROOT_B], + savedRoots: readonly string[] = [ROOT_A, ROOT_B], ) { return classifyForkability( events.map((entry) => ({ @@ -57,7 +57,7 @@ function classify( event: entry.event, workspaceRootId: entry.root ?? ROOT_A, })), - { retainedRoots: new Set(retainedRoots) }, + { savedRoots: new Set(savedRoots) }, ); } @@ -74,11 +74,11 @@ function candidates( })); } -const RUN_RECORD = retained("workflow_run", "workflow_run", { +const RUN_RECORD = saved("workflow_run", "workflow_run", { runId: "source-1", bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", }); -const ROOT_IMPORT = retained("import_component", "__root__", { +const ROOT_IMPORT = saved("import_component", "__root__", { kind: "repository", path: "flows/release.md", content: "# Release\n", @@ -89,8 +89,8 @@ describe("Tier WFK — forkability and fork selection", () => { const forkability = classify([ { id: "e1", event: RUN_RECORD }, { id: "e2", event: ROOT_IMPORT }, - { id: "e3", event: retained("workspace_file", "write:x:/notes.md"), root: ROOT_B }, - { id: "e4", event: retained("exec", "exec:echo hi"), root: ROOT_B }, + { id: "e3", event: saved("workspace_file", "write:x:/notes.md"), root: ROOT_B }, + { id: "e4", event: saved("exec", "exec:echo hi"), root: ROOT_B }, { id: "e5", event: closed("root"), root: ROOT_B }, ]); @@ -102,9 +102,9 @@ describe("Tier WFK — forkability and fork selection", () => { it("WFK2: a blocker is cumulative and names the earliest event that introduced it", function* () { const forkability = classify([ { id: "e1", event: RUN_RECORD }, - { id: "e2", event: retained("agent_prompt", "prompt:1") }, - { id: "e3", event: retained("exec", "exec:echo hi") }, - { id: "e4", event: retained("agent_prompt", "prompt:2") }, + { id: "e2", event: saved("agent_prompt", "prompt:1") }, + { id: "e3", event: saved("exec", "exec:echo hi") }, + { id: "e4", event: saved("agent_prompt", "prompt:2") }, ]); expect(forkability[0]?.forkable).toBe(true); @@ -116,15 +116,15 @@ describe("Tier WFK — forkability and fork selection", () => { } }); - it("WFK3: each stable code has its own retained cause", function* () { + it("WFK3: each stable code has its own saved cause", function* () { const forkability = classify( [ { id: "e1", event: RUN_RECORD }, // A Git-host event holding no completed reconciliation record: the run // stopped without establishing what happened at the remote. - { id: "e2", event: retained("git_host_effect", "git-push:1") }, - { id: "e3", event: retained("something_a_later_build_wrote", "whatever") }, - { id: "e4", event: retained("exec", "exec:echo hi"), root: "c".repeat(64) }, + { id: "e2", event: saved("git_host_effect", "git-push:1") }, + { id: "e3", event: saved("something_a_later_build_wrote", "whatever") }, + { id: "e4", event: saved("exec", "exec:echo hi"), root: "c".repeat(64) }, ], [ROOT_A], ); @@ -141,7 +141,7 @@ describe("Tier WFK — forkability and fork selection", () => { { code: "unsupported-effect", eventId: "e3" }, { code: "workspace-root-unavailable", eventId: "e4" }, ]); - // Codes and event ids, and nothing a retained description held. + // Codes and event ids, and nothing a saved description held. for (const entry of forkability) { for (const blocker of entry.blockers) { expect(JSON.stringify(blocker)).not.toContain("git-push"); @@ -155,7 +155,7 @@ describe("Tier WFK — forkability and fork selection", () => { // type. A completed reconciliation record carries the pre-state, the // observations, the decision and the result, and replays without // contacting a provider at all. - const completed = retained("git_host_effect", "git-push:1", { + const completed = saved("git_host_effect", "git-push:1", { request: { identity: { runId: "source-1", expansionId: "x" }, kind: "git-push", @@ -171,7 +171,7 @@ describe("Tier WFK — forkability and fork selection", () => { const forkability = classify([ { id: "e1", event: RUN_RECORD }, { id: "e2", event: completed }, - { id: "e3", event: retained("exec", "exec:echo hi") }, + { id: "e3", event: saved("exec", "exec:echo hi") }, ]); expect(forkability.map((entry) => entry.forkable)).toEqual([true, true, true]); @@ -179,7 +179,7 @@ describe("Tier WFK — forkability and fork selection", () => { // A record that is nearly one is still not one: a member the shape does not // declare describes something else, and a fork does not guess at it. - const nearly = retained("git_host_effect", "git-push:2", { + const nearly = saved("git_host_effect", "git-push:2", { request: { identity: { runId: "source-1", expansionId: "y" }, kind: "git-push", @@ -201,7 +201,7 @@ describe("Tier WFK — forkability and fork selection", () => { it("WFK3c: a record this build cannot read in full is not a completed one", function* () { // The effect belongs to `@executablemd/git` now, so this module reads its - // retained rows as compatibility data. Reading them *totally* is what makes + // saved rows as compatibility data. Reading them *totally* is what makes // the answer safe: a value JSON cannot express is a record this build // cannot classify, and classifying it as completed would hand a fork a // prefix it cannot actually replay. @@ -305,9 +305,9 @@ describe("Tier WFK — forkability and fork selection", () => { const history = candidates([ { id: "e1", event: RUN_RECORD }, { id: "e2", event: ROOT_IMPORT }, - { id: "e3", event: retained("import_component", "File") }, - { id: "e4", event: retained("workspace_file", "write:x:/notes.md"), root: ROOT_B }, - { id: "e5", event: retained("exec", "exec:echo hi"), root: ROOT_B }, + { id: "e3", event: saved("import_component", "File") }, + { id: "e4", event: saved("workspace_file", "write:x:/notes.md"), root: ROOT_B }, + { id: "e5", event: saved("exec", "exec:echo hi"), root: ROOT_B }, { id: "e6", event: closed("root"), root: ROOT_B }, ]); @@ -335,11 +335,11 @@ describe("Tier WFK — forkability and fork selection", () => { expect(journal[3]).toEqual(history[3]?.event); }); - it("WFK5: a checkpoint nobody retained, an outcome and a blocked prefix are refused", function* () { + it("WFK5: a checkpoint nobody saved, an outcome and a blocked prefix are refused", function* () { const history = candidates([ { id: "e1", event: RUN_RECORD }, { id: "e2", event: ROOT_IMPORT }, - { id: "e3", event: retained("agent_prompt", "prompt:1") }, + { id: "e3", event: saved("agent_prompt", "prompt:1") }, { id: "e4", event: closed("root") }, ]); diff --git a/packages/workflow/tests/workflow-lifecycle-authority.test.ts b/packages/workflow/tests/workflow-lifecycle-authority.test.ts index 11fb23d7b..1d77292da 100644 --- a/packages/workflow/tests/workflow-lifecycle-authority.test.ts +++ b/packages/workflow/tests/workflow-lifecycle-authority.test.ts @@ -487,7 +487,7 @@ describe("Tier WLA — a source-bundle creation", () => { // The caller's array really did change afterwards, and the run did not. // // This is the weaker of the two ownership claims: SQLite binds a parameter - // synchronously, so a transition that retained the caller's array rather + // synchronously, so a transition that saved the caller's array rather // than a copy would still store these bytes. What the copy protects is the // window between verification and persistence, and the observable that // discriminates it is `verifySourceBundleSnapshot`'s own answer — WD49. @@ -498,7 +498,7 @@ describe("Tier WLA — a source-bundle creation", () => { }); // And the run still reads back as the definition it claims, which is the - // check a drifted retained byte would fail. + // check a drifted saved byte would fail. const resumed = yield* withRunHost(root, function* (transitions) { return yield* withExecutor("bundle-copy", function* (executorLock) { return yield* transitions.begin(executorLock, { runId: "bundle-copy", action: "resume" }); @@ -529,7 +529,7 @@ describe("Tier WLA — a source-bundle creation", () => { expect(refused.ok).toBe(false); expect(!refused.ok && refused.error).toBeInstanceOf(WorkflowRequestError); - // Nothing was retained, so nothing recognizes the id: opening a connection + // Nothing was saved, so nothing recognizes the id: opening a connection // leaves an empty file behind, and an empty file is not a run. expect(yield* undiscoverable(root, "bundle-lie")).toBe(true); }); diff --git a/packages/workflow/tests/workflow-lifecycle-control.test.ts b/packages/workflow/tests/workflow-lifecycle-control.test.ts index 0fc2e78ed..5efee7a72 100644 --- a/packages/workflow/tests/workflow-lifecycle-control.test.ts +++ b/packages/workflow/tests/workflow-lifecycle-control.test.ts @@ -49,7 +49,7 @@ function withLifecycle(root: string, body: () => Operation): Operation }); } -/** A run left in one retained state, by a workflow executor that is gone. */ +/** A run left in one saved state, by a workflow executor that is gone. */ function* runEndedAs( root: string, runId: string, @@ -132,7 +132,7 @@ describe("Tier WLC — cancellation and deletion", () => { }); }); - it("WLC2: a run with no live workflow executor follows its retained state", function* () { + it("WLC2: a run with no live workflow executor follows its saved state", function* () { const root = yield* useStorageRoot(); // Nothing running, nothing recorded: cancellable directly. @@ -291,7 +291,7 @@ describe("Tier WLC — cancellation and deletion", () => { if (!removed.ok) { throw removed.error; } - // Only what actually went, and the lock is not retained run state. + // Only what actually went, and the lock is not saved run state. expect(removed.value.removed).toEqual(["run-storage"]); // Absent is reported rather than treated as an idempotent success. @@ -314,7 +314,7 @@ describe("Tier WLC — cancellation and deletion", () => { * * Between the moment a creation transaction commits and the moment its caller * has registered anything of its own there is real work: the begin transition - * reads its retained source back and authenticates it, the caller reports the + * reads its saved source back and authenticates it, the caller reports the * run id, projects the closure and imports the Deno adapter. Every one of those * suspends, and a cancellation arriving in any of them used to leave the * durable run and its execution saying `running` while the executor lock was @@ -359,7 +359,7 @@ describe("Tier WLT — interrupted after the creation transaction committed", () return n; } - interface Retained { + interface Saved { readonly status: unknown; readonly reasonKind: unknown; readonly reasonCode: unknown; @@ -369,7 +369,7 @@ describe("Tier WLT — interrupted after the creation transaction committed", () readonly events: number; } - function retained(root: string, runId: string): Retained { + function saved(root: string, runId: string): Saved { const database = new DatabaseSync(workflowRunPath(root, runId), { readOnly: true }); try { const run = row(database.prepare("SELECT * FROM workflow_run").get(), "the run"); @@ -390,7 +390,7 @@ describe("Tier WLT — interrupted after the creation transaction committed", () "ON b.source_hash = s.source_hash ORDER BY s.path ASC", ) .all(), - "a retained source", + "a saved source", ), events: count( database.prepare("SELECT count(*) AS n FROM journal_events").get(), @@ -404,7 +404,7 @@ describe("Tier WLT — interrupted after the creation transaction committed", () /** Everything both post-commit windows owe, whichever one produced them. */ function* settledInterrupted(root: string, runId: string): Operation { - const after = retained(root, runId); + const after = saved(root, runId); // The complete version-2 snapshot is durable: the descriptor, and the exact // bytes behind every path it names. Read through the same parser a host @@ -445,11 +445,11 @@ describe("Tier WLT — interrupted after the creation transaction committed", () // have written what they saw. Acquiring is what proves the release. yield* withRunHost(root, function* () { yield* withExecutor(runId, function* () { - expect(retained(root, runId).status).toBe("interrupted"); + expect(saved(root, runId).status).toBe("interrupted"); }); }); - // And the run resumes from the bytes it retained, not from anything a host + // And the run resumes from the bytes it saved, not from anything a host // would have to go and find. yield* withRunHost(root, function* (transitions) { yield* withExecutor(runId, function* (executorLock) { @@ -459,11 +459,11 @@ describe("Tier WLT — interrupted after the creation transaction committed", () } const { sources } = resumed.value; expect(sources.sources.map((one) => one.path)).toEqual([BUNDLE_ENTRYPOINT]); - const retainedBytes = sources.sources[0]?.bytes; - if (retainedBytes === undefined) { + const savedBytes = sources.sources[0]?.bytes; + if (savedBytes === undefined) { throw new Error("the resumed run retains no entrypoint"); } - expect(new TextDecoder().decode(retainedBytes)).toBe(BUNDLE_SOURCE); + expect(new TextDecoder().decode(savedBytes)).toBe(BUNDLE_SOURCE); }); }); } @@ -478,7 +478,7 @@ describe("Tier WLT — interrupted after the creation transaction committed", () }; // The first time the connection lock is taken after the creation - // transaction committed is source settlement reading the retained bytes + // transaction committed is source settlement reading the saved bytes // back to authenticate them. Holding that acquisition open is what puts // this case inside the window by construction rather than by winning a race // against it: the transition is provably still in `settleSources`, because @@ -489,7 +489,7 @@ describe("Tier WLT — interrupted after the creation transaction committed", () /** Whether the creation transaction has committed, asked of the file. */ function committed(): boolean { try { - return retained(root, runId).executions.length === 1; + return saved(root, runId).executions.length === 1; } catch { // No database, or no table in it yet: nothing has committed. return false; @@ -544,9 +544,9 @@ describe("Tier WLT — interrupted after the creation transaction committed", () // transition has not returned. yield* arrived.operation; expect(returned).toBe(false); - expect(retained(root, runId).status).toBe("running"); - expect(retained(root, runId).executions).toHaveLength(1); - expect(retained(root, runId).executions[0]?.["stopped_at"]).toBe(null); + expect(saved(root, runId).status).toBe("running"); + expect(saved(root, runId).executions).toHaveLength(1); + expect(saved(root, runId).executions[0]?.["stopped_at"]).toBe(null); // Halted where it stands, and the acquisition torn down under it — // structured cancellation, inside the window. @@ -584,8 +584,8 @@ describe("Tier WLT — interrupted after the creation transaction committed", () // Exactly where the command stands when it reports the run id, // projects the closure and imports the Deno adapter: admitted, running, // and with nothing of its own registered yet. The scope ends here. - expect(retained(root, runId).status).toBe("running"); - expect(retained(root, runId).executions[0]?.["stopped_at"]).toBe(null); + expect(saved(root, runId).status).toBe("running"); + expect(saved(root, runId).executions[0]?.["stopped_at"]).toBe(null); }); }); diff --git a/packages/workflow/tests/workflow-lifecycle-inspection.test.ts b/packages/workflow/tests/workflow-lifecycle-inspection.test.ts index 70058982d..1be683d9b 100644 --- a/packages/workflow/tests/workflow-lifecycle-inspection.test.ts +++ b/packages/workflow/tests/workflow-lifecycle-inspection.test.ts @@ -73,7 +73,7 @@ import { * * `Assert` is the only instantiation that type-checks, so this stops * compiling the moment `WorkflowArtifactSnapshot` gains the member again — - * including by going back to extending the retained-run snapshot, which is how + * including by going back to extending the saved-run snapshot, which is how * it acquired it the first time. */ type Assert = T; @@ -92,21 +92,21 @@ type SharedIsRetrievalFree = Assert< >; const SHARED_IS_RETRIEVAL_FREE: SharedIsRetrievalFree = false; -/** A retained snapshot is the one that still has it. */ -type RetainedKeepsRetrieval = Assert< +/** A saved snapshot is the one that still has it. */ +type SavedKeepsRetrieval = Assert< "retrieval" extends keyof WorkflowLifecycleSnapshot ? false : true >; -const RETAINED_KEEPS_RETRIEVAL: RetainedKeepsRetrieval = false; +const SAVED_KEEPS_RETRIEVAL: SavedKeepsRetrieval = false; const { history, inspect, list } = WorkflowLifecycle.operations; /** - * A run with a settled record, one execution and three retained events. + * A run with a settled record, one execution and three saved events. * * The events name the run they belong to, so two runs of this fixture are told * apart by what they retain and not only by the ids SQLite happened to assign. */ -function* retainedRun(root: string, runId: string): Operation { +function* savedRun(root: string, runId: string): Operation { yield* withRunHost(root, function* (transitions) { yield* withExecutorRun( transitions, @@ -238,7 +238,7 @@ function* historyOf(runId: string): Operation { describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI1: one snapshot describes the record, executions, frontier and root together", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); yield* withLifecycle(root, function* () { const snapshot = yield* snapshotOf("release-1.4"); @@ -280,7 +280,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI3: status, list and history change no byte, row or mode", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); const path = runPath(root, "release-1.4"); const before = yield* fingerprint(path); @@ -295,8 +295,8 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI4: list orders newest update first and reads only the run namespace", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "older"); - yield* retainedRun(root, "newer"); + yield* savedRun(root, "older"); + yield* savedRun(root, "newer"); // Names outside the hashed `.sqlite` namespace are the provider's own // arrangement, and a lifecycle sidecar is never a database candidate. yield* writeTextFile(join(root, "lifecycle.lock"), ""); @@ -313,7 +313,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI5: one validly named foreign candidate fails the whole list", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); // Named exactly the way a run is named, and holding something else. const candidate = join(root, `${"a".repeat(64)}.sqlite`); yield* writeTextFile(candidate, "not a workflow run database"); @@ -331,7 +331,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI5b: a healthy database at another run's name is not a second run", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); const original = runPath(root, "release-1.4"); // A perfectly good version-1 workflow database, recognized in every // structural way, sitting where another run id would put it. Nothing about @@ -362,9 +362,9 @@ describe("Tier WLI — immutable lifecycle inspection", () => { expect(yield* fileFingerprint(copy)).toEqual(before.copy); }); - it("WLI5c: a retained id whose stored bytes no reader sees fails the whole list", function* () { + it("WLI5c: a saved id whose stored bytes no reader sees fails the whole list", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); // An id SQLite stores every byte of and every reader sees only the first // seven characters of: `length()` says 7 where the blob says 14, and // node:sqlite hands back the truncated string. Bound parameters truncate at @@ -372,7 +372,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { // // What refuses this is the location check, not the run-id rule: the reader // never sees a NUL, so what it compares is `release` against a file named - // for `release\u0000shadow`. Removing the retained run-id parity leaves + // for `release\u0000shadow`. Removing the saved run-id parity leaves // this test passing, and removing the location check fails it — which is // why the parity is a contract rather than something this proves. const shadowed = `release\u0000shadow`; @@ -406,9 +406,9 @@ describe("Tier WLI — immutable lifecycle inspection", () => { expect(yield* fileFingerprint(candidate)).toEqual(before.candidate); }); - it("WLI6: history is every retained event with its exact id, root and source", function* () { + it("WLI6: history is every saved event with its exact id, root and source", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); const stored = storedEvents(runPath(root, "release-1.4")); yield* withLifecycle(root, function* () { @@ -436,7 +436,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI18: a generated operation exposes the fragment it was written in", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); const path = runPath(root, "release-1.4"); // What the engine records for an operation inside admitted generated source: // the fragment's own identity, and no path, because generated text is not a @@ -474,7 +474,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI19: a source naming both a file and a fragment makes the entry unreadable", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); const path = runPath(root, "release-1.4"); // Two answers to where one operation was written. Reading either would be // choosing which of them to believe. @@ -510,7 +510,7 @@ describe("Tier WLI — immutable lifecycle inspection", () => { it("WLI7: a present source that does not parse makes the entry unreadable", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-1.4"); + yield* savedRun(root, "release-1.4"); const path = runPath(root, "release-1.4"); const secret = "s3cret-position-value"; tamper(path, (database) => { @@ -600,9 +600,9 @@ describe("Tier WLI — immutable lifecycle inspection", () => { }); it("WLI9: an id holding * or ? addresses exactly its own run", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "release-*"); - yield* retainedRun(root, "release-?"); - yield* retainedRun(root, "release-1"); + yield* savedRun(root, "release-*"); + yield* savedRun(root, "release-?"); + yield* savedRun(root, "release-1"); const reached: string[] = []; yield* scoped(function* () { @@ -836,7 +836,7 @@ function recoverInPlace(path: string): void { } } -/** Everything about the retained pair that inspection may not change. */ +/** Everything about the saved pair that inspection may not change. */ interface PairPrint { readonly database: string; readonly journal: string; @@ -906,7 +906,7 @@ function withObserved( describe("Tier WLI — inspecting a crashed run", () => { it("WLI10: a hot run reports its last committed state, and stays hot", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "hot-1"); + yield* savedRun(root, "hot-1"); const path = runPath(root, "hot-1"); // What the run committed, read while it is still readable. @@ -949,7 +949,7 @@ describe("Tier WLI — inspecting a crashed run", () => { "before-cleanup", ]); - // The retained pair is byte-for-byte what the crash left, and still hot: + // The saved pair is byte-for-byte what the crash left, and still hot: // recovering it is the next write-capable owner's job, not inspection's. expect(yield* pairPrint(path)).toEqual(before); expect(directCode(path)).toBe(READONLY_ROLLBACK); @@ -960,9 +960,9 @@ describe("Tier WLI — inspecting a crashed run", () => { it("WLI11: list answers a healthy run and a hot one together, one copy at a time", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "healthy-1"); - yield* retainedRun(root, "hot-2"); - yield* retainedRun(root, "hot-3"); + yield* savedRun(root, "healthy-1"); + yield* savedRun(root, "hot-2"); + yield* savedRun(root, "hot-3"); // The order these are answered in while every one of them is readable. const ordered = yield* withLifecycle(root, function* () { @@ -1011,7 +1011,7 @@ describe("Tier WLI — inspecting a crashed run", () => { it("WLI12: a copy is taken before any owner may recover, and a recovered source is read as itself", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "race-1"); + yield* savedRun(root, "race-1"); yield* leaveHot(root, "race-1"); const path = runPath(root, "race-1"); @@ -1063,7 +1063,7 @@ describe("Tier WLI — inspecting a crashed run", () => { // The other way round: an owner holds coordination and recovers the source // while inspection is already waiting for it. The coordinated retry reads // the run as itself, so nothing is ever copied. - yield* retainedRun(root, "race-2"); + yield* savedRun(root, "race-2"); yield* leaveHot(root, "race-2"); const second = recorder(); const holding = withResolvers(); @@ -1124,7 +1124,7 @@ describe("Tier WLI — inspecting a crashed run", () => { }, { runId: "content-1", - what: "a retained row that does not describe what its column claims", + what: "a saved row that does not describe what its column claims", *damage(_root, path) { // A column SQLite is satisfied by and a reader is not: the text is // there, and it is not the timestamp the record says it holds. @@ -1155,8 +1155,8 @@ describe("Tier WLI — inspecting a crashed run", () => { for (const planted of damages) { const root = yield* useStorageRoot(); - yield* retainedRun(root, "healthy-source"); - yield* retainedRun(root, planted.runId); + yield* savedRun(root, "healthy-source"); + yield* savedRun(root, planted.runId); const path = runPath(root, planted.runId); yield* planted.damage(root, path); @@ -1185,7 +1185,7 @@ describe("Tier WLI — inspecting a crashed run", () => { expect(answered.error.message).toBe(expected.message); expect(answered.error).not.toBeInstanceOf(WorkflowInspectionRecoveryError); // A refusal that names a file names the run's own. One that names a - // retained location instead has no path to get wrong. + // saved location instead has no path to get wrong. if (expected.message.includes(path)) { expect(answered.error.message).toContain(path); } @@ -1270,7 +1270,7 @@ describe("Tier WLI — inspecting a crashed run", () => { for (const fault of cleaned) { const root = yield* useStorageRoot(); - yield* retainedRun(root, "fault-1"); + yield* savedRun(root, "fault-1"); const path = runPath(root, "fault-1"); yield* leaveHot(root, "fault-1"); const before = yield* pairPrint(path); @@ -1293,7 +1293,7 @@ describe("Tier WLI — inspecting a crashed run", () => { // nothing to name. expect(failure.scratchPath).toBeUndefined(); expect(failure.cause).toBeUndefined(); - // The condition, not SQLite's words for it and not anything retained. + // The condition, not SQLite's words for it and not anything saved. expect(failure.message).not.toContain("SQLITE"); expect(failure.message).not.toContain("readonly"); for (const directory of watched.directories) { @@ -1314,7 +1314,7 @@ describe("Tier WLI — inspecting a crashed run", () => { // `Result` to return because nobody is waiting for one. { const root = yield* useStorageRoot(); - yield* retainedRun(root, "cancel-1"); + yield* savedRun(root, "cancel-1"); const path = runPath(root, "cancel-1"); yield* leaveHot(root, "cancel-1"); const before = yield* pairPrint(path); @@ -1350,7 +1350,7 @@ describe("Tier WLI — inspecting a crashed run", () => { for (const cancelled of [false, true]) { const root = yield* useStorageRoot(); const runId = cancelled ? "residue-2" : "residue-1"; - yield* retainedRun(root, runId); + yield* savedRun(root, runId); const path = runPath(root, runId); yield* leaveHot(root, runId); const before = yield* pairPrint(path); @@ -1416,7 +1416,7 @@ describe("Tier WLI — inspecting a crashed run", () => { // receiver either: it has to be raised. { const root = yield* useStorageRoot(); - yield* retainedRun(root, "residue-3"); + yield* savedRun(root, "residue-3"); const path = runPath(root, "residue-3"); yield* leaveHot(root, "residue-3"); const before = yield* pairPrint(path); @@ -1470,7 +1470,7 @@ describe("Tier WLI — inspecting a crashed run", () => { it("WLI17: a connection that is already open owns the pair until it closes", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "cached-1"); + yield* savedRun(root, "cached-1"); const path = runPath(root, "cached-1"); yield* scoped(function* () { @@ -1507,7 +1507,7 @@ describe("Tier WLI — inspecting a crashed run", () => { throw answered.error; } expect(answered.value.record.runId).toBe("cached-1"); - // It recovered a copy, and the retained pair is still the crash's. + // It recovered a copy, and the saved pair is still the crash's. expect(watched.phases).toContain("source-pair-copied"); expect(directCode(path)).toBe(READONLY_ROLLBACK); }); @@ -1515,7 +1515,7 @@ describe("Tier WLI — inspecting a crashed run", () => { it("WLI16: the clean path costs nothing, and the hot one takes no ownership", function* () { const root = yield* useStorageRoot(); - yield* retainedRun(root, "clean-1"); + yield* savedRun(root, "clean-1"); const path = runPath(root, "clean-1"); // The run host that created it has closed, so the empty sidecar its @@ -1536,7 +1536,7 @@ describe("Tier WLI — inspecting a crashed run", () => { // Recovering a crashed run buys inspection nothing it did not already // have. The same probes WLI8 holds the clean path to, against a run that // does take the recovery path. - yield* retainedRun(root, "hot-16"); + yield* savedRun(root, "hot-16"); yield* leaveHot(root, "hot-16"); const hot = runPath(root, "hot-16"); // The executor that created this run left its own sidecar, which goes diff --git a/packages/workflow/tests/workflow-run-journal.test.ts b/packages/workflow/tests/workflow-run-journal.test.ts index d79b45177..3d56fd898 100644 --- a/packages/workflow/tests/workflow-run-journal.test.ts +++ b/packages/workflow/tests/workflow-run-journal.test.ts @@ -1,5 +1,5 @@ /** - * Tier WJ — the retained journal, and the transaction a caller can hold. + * Tier WJ — the saved journal, and the transaction a caller can hold. * * Two boundaries are under test and they are easy to confuse. The journal * boundary is ordering and identity: events go in filtered, come back in the @@ -198,7 +198,7 @@ describe("Tier WJ — appending and replaying the journal", () => { expect(serializeDurableEvent(stored[0])).toBe(serializeDurableEvent(event)); }); - it("WJ2b: every ordinary journal append records the current retained root", function* () { + it("WJ2b: every ordinary journal append records the current saved root", function* () { const root = yield* useStorageRoot(); const path = runPath(root, "release-1.4"); @@ -404,7 +404,7 @@ describe("Tier WJ — what reaches SQLite", () => { expect(seen.events).toEqual([]); }); - it("WJ5e: a retained-manifest FK failure stays a storage failure", function* () { + it("WJ5e: a saved-manifest FK failure stays a storage failure", function* () { const root = yield* useStorageRoot(); const path = runPath(root, "release-1.4"); @@ -1585,7 +1585,7 @@ describe("Tier WJ — surviving a process", () => { expect(second.code).toBe(0); // Every durable operation ran once. The second process restored their - // results from the retained journal instead of performing them again. + // results from the saved journal instead of performing them again. expect(yield* readTextFile(marker)).toBe("first\nsecond\nthird\n"); const before = JSON.parse(first.out); @@ -1610,7 +1610,7 @@ describe("Tier WJ — surviving a process", () => { * * The record is one closed union now. A reader accepts either member's exact * set in any key order and no other, and the ordinary serializer writes one - * spelling per version — which is what keeps a retained version-1 record byte + * spelling per version — which is what keeps a saved version-1 record byte * for byte what it always was while version 2 invents no Git field. */ describe("Tier WJ — the source-bundle run record", () => { @@ -1640,7 +1640,7 @@ describe("Tier WJ — the source-bundle run record", () => { for (const refused of [ // Members this shape does not declare, including the ones a record - // retained under an older format carried. A run read loosely would be a + // saved under an older format carried. A run read loosely would be a // run this build cannot account for. { runId: "r" }, { runId: "r", bundleHash: BUNDLE_HASH, base: "main" }, diff --git a/packages/workflow/tests/workflow-run-storage.test.ts b/packages/workflow/tests/workflow-run-storage.test.ts index d78feeef7..b929c7140 100644 --- a/packages/workflow/tests/workflow-run-storage.test.ts +++ b/packages/workflow/tests/workflow-run-storage.test.ts @@ -1,5 +1,5 @@ /** - * Tier WS — retained workflow runs, through the production Deno adapter. + * Tier WS — saved workflow runs, through the production Deno adapter. * * Nothing here is a stand-in. Every test writes a real SQLite file into a real * directory and reads it back, because what is being checked is what survives a @@ -1157,7 +1157,7 @@ describe("Tier WS — refusing what is not this run's database", () => { expect(yield* until(readFile(path))).toEqual(before); }); - it("WS22c: partial DOFS and retained-root initialization are corruption and stay unchanged", function* () { + it("WS22c: partial DOFS and saved-root initialization are corruption and stay unchanged", function* () { const root = yield* useStorageRoot(); const partials: Array<[string, (database: DatabaseSync) => void]> = [ [ @@ -1307,7 +1307,7 @@ describe("Tier WS — refusing what is not this run's database", () => { throw settled.error; } - // Deliberately impossible retained state: no supported transition + // Deliberately impossible saved state: no supported transition // writes a stop time that is not a time, so the row is edited directly. tamper(runPath(root, "bad-stop-time"), (raw) => { raw.prepare("UPDATE document_executions SET stopped_at = 'whenever'").run(); @@ -1605,7 +1605,7 @@ describe("Tier WS — version 1 amended in place", () => { initializeShapeWithout(database, [...AMENDED_TABLES, ...ANSWER_TABLES, ...FORK_TABLES]); } - /** The shape complete before retained answer delivery joined version 1. */ + /** The shape complete before saved answer delivery joined version 1. */ function initializePreAnswerVersionOne(database: DatabaseSync): void { initializeShapeWithout(database, [...ANSWER_TABLES, ...FORK_TABLES]); } @@ -1701,7 +1701,7 @@ describe("Tier WS — version 1 amended in place", () => { expect(!result.ok && result.error).toBeInstanceOf(WorkflowDatabaseCorruptError); }); - it("WS23d: a malformed retained row is reported rather than repaired", function* () { + it("WS23d: a malformed saved row is reported rather than repaired", function* () { const database = new DatabaseSync(":memory:"); try { // The constraints version 1 declares refuse these values outright, so the @@ -1735,7 +1735,7 @@ describe("Tier WS — version 1 amended in place", () => { } }); - it("WS33a: version 1 declares the retained answer table", function* () { + it("WS33a: version 1 declares the saved answer table", function* () { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { yield* createRun(); @@ -1753,7 +1753,7 @@ describe("Tier WS — version 1 amended in place", () => { } }); - it("WS33b: the shape before retained delivery is refused unchanged", function* () { + it("WS33b: the shape before saved delivery is refused unchanged", function* () { const root = yield* useStorageRoot(); const path = runPath(root, "release-1.4"); tamper(path, initializePreAnswerVersionOne); @@ -1840,7 +1840,7 @@ describe("Tier WS — version 1 amended in place", () => { /** * The Git record a lookup answered with, narrowed rather than asserted. * - * The retained record is a closed union now, and the base these cases assert + * The saved record is a closed union now, and the base these cases assert * about is a member only the Git one has. */ function gitRecord(record: WorkflowRunRecord): WorkflowRunRecord { diff --git a/packages/workflow/tests/workflow-run.test.ts b/packages/workflow/tests/workflow-run.test.ts index 00eb0498a..67d3f83f7 100644 --- a/packages/workflow/tests/workflow-run.test.ts +++ b/packages/workflow/tests/workflow-run.test.ts @@ -35,7 +35,7 @@ import { GitQuery } from "@executablemd/git/api"; import { createWorkflowRunInstallation, getWorkflowRun } from "../src/run.ts"; import { WORKFLOW_RUN } from "../src/journal.ts"; import type { WorkflowRun } from "../src/run.ts"; -import { retainedRunMismatch } from "../mod.ts"; +import { savedRunMismatch } from "../mod.ts"; const BUNDLE = "9fceb02d0ae598e95dc970b74767f19372d61af89fceb02d0ae598e95dc970b74"; const OTHER_BUNDLE = "1111111111111111111111111111111111111111111111111111111111111111"; @@ -70,7 +70,7 @@ function runInstallation( }, agree(recorded: WorkflowRun): WorkflowRun { if (recorded.bundleHash !== bundleHash) { - throw retainedRunMismatch(["bundleHash"]); + throw savedRunMismatch(["bundleHash"]); } return recorded; }, @@ -550,7 +550,7 @@ describe("Tier WR — workflow runs", () => { description: { type: "workflow_run", name: "workflow_run", bundleHash: BUNDLE }, result: { status: "ok", value: { runId: "seeded-run", bundleHash: BUNDLE } }, }); - // The root import a real completed run always records. A retained terminal + // The root import a real completed run always records. A saved terminal // result is reused on the strength of the selection its root import // established, so a journal that carries one without the other describes a // run that never happened and is refused. @@ -690,7 +690,7 @@ describe("Tier WR — workflow runs", () => { // package reads the run through its own descriptor of the same context name. it("WR17: a descriptor of the same name built elsewhere reads the run", function* () { // The binding is the execution's own slot, and what it holds is the exact - // frozen run. Retained-history admission and durable preparation both + // frozen run. Saved-history admission and durable preparation both // decide the run from outside the scope that reads it, so a context set // from either would end with the operation that set it; the slot is what // gives them somewhere to put it that the document can still see. @@ -748,7 +748,7 @@ describe("Tier WR — workflow runs", () => { * Tier WM — what public middleware cannot do to a workflow run. * * The run is prepared by canonical core inside the durable root, after - * retained-history admission and before any public `Execution.document` + * saved-history admission and before any public `Execution.document` * policy. These are the ways a handler might try to get in front of that, and * what happens instead. */ @@ -895,7 +895,7 @@ describe("Tier WR — workflow runs", () => { /** * What a host outside this package supplies, and what it does not. * - * `runInstallation(bundleHash)` and `retainedWorkflowInstallation(run)` are + * `runInstallation(bundleHash)` and `savedWorkflowInstallation(run)` are * both this constructor with a different preparation, so a host that resolves * its own repository supplies the same four terms. What it never supplies is * when the admission runs, what parses the record, or where the run is @@ -942,7 +942,7 @@ describe("Tier WR — workflow runs", () => { }); }); - it("WR22: a host preparation's agreement decides a retained history", function* () { + it("WR22: a host preparation's agreement decides a saved history", function* () { const stream = new InMemoryStream(); yield* stream.append({ type: "yield", diff --git a/packages/workflow/tests/workflow-suspension-answer.test.ts b/packages/workflow/tests/workflow-suspension-answer.test.ts index acee243c5..2ef4e81f7 100644 --- a/packages/workflow/tests/workflow-suspension-answer.test.ts +++ b/packages/workflow/tests/workflow-suspension-answer.test.ts @@ -3,10 +3,10 @@ * * Two operations that must not be confused, proved against a real run in a real * file. **Delivery** happens while nothing is running: it judges a value against - * the schema the wait retained, crosses the secret gate, and retains it. It + * the schema the wait saved, crosses the secret gate, and retains it. It * appends no journal event, begins no document execution and leaves the run * `suspended`. **Resume** is what spends it: the execution reaches the same - * wait, publishes one durable answer event and consumes the retained value in + * wait, publishes one durable answer event and consumes the saved value in * one transaction, and the document continues from the value. * * The invariants worth stating carefully are the ones a weaker implementation @@ -25,7 +25,7 @@ import type { DurableEvent, Json } from "@executablemd/durable-streams"; import { collect, inlineSource, registerComponents } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; import type { Result } from "effection"; -import { retainedWorkflowInstallation } from "../src/run.ts"; +import { savedWorkflowInstallation } from "../src/run.ts"; import { SUSPENSION_ANSWER, WorkflowInputDelivery, type WorkflowAnswerRetention } from "../mod.ts"; import { SUSPENSION_REQUEST, suspendFor } from "../src/suspension/suspend.ts"; import { createSuspensionController } from "../src/deno/suspension.ts"; @@ -74,7 +74,7 @@ interface Attempted { * * The controller stands in for the executor, exactly as the CLI arranges it: * it observes a reported wait, `race` halts the execution around it, and the - * run is settled `suspended` with a stop reason naming the retained request — + * run is settled `suspended` with a stop reason naming the saved request — * which is the state delivery reads. A run that did not wait is left alone. */ function attempt( @@ -122,7 +122,7 @@ function attempt( call(function* (): Operation { yield* collect( yield* executeInstalled({ ...inlineSource("\n"), stream }, [ - retainedWorkflowInstallation({ + savedWorkflowInstallation({ runId: RUN_ID, bundleHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", @@ -221,7 +221,7 @@ function deliver( }); } -/** Every retained answer row, read the way something outside XMD would. */ +/** Every saved answer row, read the way something outside XMD would. */ function answerRows(root: string, runId = RUN_ID): Record[] { const database = new DatabaseSync(runPath(root, runId)); try { @@ -283,7 +283,7 @@ describe("Tier WAD — a delivered answer, and the resume that spends it", () => expect(JSON.parse(String(rows[0]?.["answer"]))).toEqual(ANSWER); }); - it("WAD2: a value the retained schema refuses is not retained", function* () { + it("WAD2: a value the saved schema refuses is not saved", function* () { const root = yield* useStorageRoot(); const id = yield* suspendedRun(root); @@ -462,7 +462,7 @@ describe("Tier WAD — a delivered answer, and the resume that spends it", () => expect(consumedAt).toEqual(expect.any(String)); // Nothing is delivered for the second wait, so this resume replays the - // first one's answer out of its retained event and stops at the second + // first one's answer out of its saved event and stops at the second // again. The value comes back, and no second event and no second // consumption come with it. const replayed = yield* attempt(root, "resume", twoWaits); @@ -513,7 +513,7 @@ describe("Tier WAD — a delivered answer, and the resume that spends it", () => database.exec(` CREATE TRIGGER refuse_consume BEFORE UPDATE ON workflow_suspension_answers BEGIN - SELECT raise(ABORT, 'the retained answer refuses to be consumed'); + SELECT raise(ABORT, 'the saved answer refuses to be consumed'); END `); }); @@ -531,7 +531,7 @@ describe("Tier WAD — a delivered answer, and the resume that spends it", () => }); }); -/** The run's retained history, read through a host installation of its own. */ +/** The run's saved history, read through a host installation of its own. */ function attemptEvents(root: string): Operation { return scoped(function* () { const database = new DatabaseSync(runPath(root, RUN_ID)); diff --git a/packages/workflow/tests/workflow-suspension.test.ts b/packages/workflow/tests/workflow-suspension.test.ts index dc8376d3b..0b83c3bad 100644 --- a/packages/workflow/tests/workflow-suspension.test.ts +++ b/packages/workflow/tests/workflow-suspension.test.ts @@ -42,7 +42,7 @@ import { import { durablePosition } from "@executablemd/durable-streams"; import { collect, inlineSource, registerComponents } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; -import { retainedWorkflowInstallation } from "../src/run.ts"; +import { savedWorkflowInstallation } from "../src/run.ts"; import type { WorkflowRunDatabase } from "../src/storage/api.ts"; import { useStorageRoot, withBegunRun } from "./support/storage.ts"; import type { WorkflowRun } from "../src/run.ts"; @@ -64,7 +64,7 @@ const RUN: WorkflowRun = Object.freeze({ * One begun run, whose journal is what a suspension is verified against. * * A real database rather than a stream in memory, because entering a wait now - * asks the run what it retained — what admits a wait is the retained request, so a + * asks the run what it saved — what admits a wait is the saved request, so a * suite that stubbed the journal would be proving nothing about it. */ function withRun(body: (database: WorkflowRunDatabase) => Operation): Operation { @@ -82,7 +82,7 @@ interface Attempt { readonly thrown: unknown; } -/** Every `suspension_request` this stream retained, oldest first. */ +/** Every `suspension_request` this stream saved, oldest first. */ function requests(events: DurableEvent[]): DurableEvent[] { return events.filter( (event) => event.type === "yield" && event.description.type === SUSPENSION_REQUEST, @@ -90,7 +90,7 @@ function requests(events: DurableEvent[]): DurableEvent[] { } /** - * Run one document body under a retained run, until it suspends or ends. + * Run one document body under a saved run, until it suspends or ends. * * The controller stands in for the executor: it observes the reported wait, and * `race` then halts the execution — which is what the real owner does. Halting @@ -127,7 +127,7 @@ function attempt( call(function* (): Operation { yield* collect( yield* executeInstalled({ ...inlineSource("\n"), stream }, [ - retainedWorkflowInstallation(RUN), + savedWorkflowInstallation(RUN), ]), ); }), @@ -159,7 +159,7 @@ function attempt( * A durable operation built to look like a published suspension request. * * Exactly what document-side code can reach: the public effect type and the - * public factory. Yielded where the real request sits, it replays that retained + * public factory. Yielded where the real request sits, it replays that saved * row, because replay identity compares a type and a name and nothing else. */ function* forgedRequest(id: string): Workflow { @@ -314,7 +314,7 @@ describe("Tier WS — a durable wait's request and identity", () => { } yield* withRun(function* (database) { - // A request that is a well-typed `Json` and still cannot be retained: the + // A request that is a well-typed `Json` and still cannot be saved: the // journal holds what `JSON.stringify` produces, and a cycle produces // nothing. This is the case a type cannot catch. const cyclic: Record = {}; @@ -360,14 +360,14 @@ describe("Tier WS — a durable wait's request and identity", () => { }); }); - it("WS6: a retained request does not authorize a caller standing before it", function* () { + it("WS6: a saved request does not authorize a caller standing before it", function* () { yield* withRun(function* (database) { const refusals: string[] = []; const reached: string[] = []; // Document-side code that knows the provider's name and the identifier the // wait ahead of it will have — on a resume it could read that identifier - // straight out of the run's own retained history. + // straight out of the run's own saved history. function early(id: string): Operation { return call(function* () { const Same: Api = createApi( @@ -404,14 +404,14 @@ describe("Tier WS — a durable wait's request and identity", () => { }; } - // First execution: the identifier is not retained yet, and the early call + // First execution: the identifier is not saved yet, and the early call // is refused for standing in the wrong place rather than for guessing. - const first = yield* attempt(database, procedure("an-identifier-not-yet-retained")); + const first = yield* attempt(database, procedure("an-identifier-not-yet-saved")); const id = first.notice?.suspensionId ?? ""; expect(id).not.toBe(""); expect(refusals).toEqual(["refused"]); - // Resume: the request is retained now, and its exact identifier is what + // Resume: the request is saved now, and its exact identifier is what // the early call presents. It must still be refused — the row exists, but // this caller has not reached it. const resumed = yield* attempt(database, procedure(id)); @@ -450,7 +450,7 @@ describe("Tier WS — a durable wait's request and identity", () => { const id = first.notice?.suspensionId ?? ""; expect(id).not.toBe(""); - // A clean resume first: the prior effect replays, the retained request + // A clean resume first: the prior effect replays, the saved request // replays, and the real operation reports the same wait. const clean = yield* attempt(database, function* () { yield* prior(); @@ -463,7 +463,7 @@ describe("Tier WS — a durable wait's request and identity", () => { ).toHaveLength(0); // Now a resume that never calls `suspendFor()`. It replays the prior - // effect, then replays the retained request with an operation it built + // effect, then replays the saved request with an operation it built // itself — replay identity is a type and a name, so this lands on exactly // the position the real wait occupies — and enters through the provider's // public name. @@ -491,7 +491,7 @@ describe("Tier WS — a durable wait's request and identity", () => { }); // Standing at the validated wait *is* being at it. The operation that - // replayed the retained request arrived where the real one publishes, so + // replayed the saved request arrived where the real one publishes, so // it reports the same wait rather than a new one — what identifies a wait // is the request at that position, not which code reached it. expect(refusal).toBe(""); @@ -664,7 +664,7 @@ describe("Tier WS — a durable wait's request and identity", () => { expect(performed).toEqual([]); expect(attempted.notice).toBeUndefined(); - // The request is retained, because the document really did ask. The + // The request is saved, because the document really did ask. The // answer is not, because nobody delivered one — so a later resume reaches // this same wait rather than replaying an answer it invented. expect(requests(attempted.events)).toHaveLength(1); @@ -679,7 +679,7 @@ describe("Tier WS — a durable wait's request and identity", () => { }); }); - it("WS10: a malformed retained request admits no wait", function* () { + it("WS10: a malformed saved request admits no wait", function* () { yield* withRun(function* (database) { let refusal = ""; @@ -711,7 +711,7 @@ describe("Tier WS — a durable wait's request and identity", () => { } }); - // Refused for what the retained row is, not for where the caller stands. + // Refused for what the saved row is, not for where the caller stands. expect(refusal).toContain("not one a durable wait can be entered for"); expect(attempted.notice).toBeUndefined(); diff --git a/packages/workflow/tests/workspace-crash-recovery.test.ts b/packages/workflow/tests/workspace-crash-recovery.test.ts index c6c0a8d28..2ef41445d 100644 --- a/packages/workflow/tests/workspace-crash-recovery.test.ts +++ b/packages/workflow/tests/workspace-crash-recovery.test.ts @@ -135,7 +135,7 @@ function committed(path: string): Record { } } -/** The retained roots an event never named, so restoration cannot borrow them. */ +/** The saved roots an event never named, so restoration cannot borrow them. */ function manifestsOf(path: string, rootId: string): string[] { const sqlite = new DatabaseSync(path, { readOnly: true }); try { @@ -256,8 +256,8 @@ describe("Tier WAC — Workspace effects across a process boundary", () => { ready: true, content: CRASH_CONTENT, currentRoot: expect.any(String), - retainedRoots: 3, - currentRootRetained: 1, + savedRoots: 3, + currentRootSaved: 1, journalEventId: expect.any(String), journalRootId: during.announcement["currentRoot"], journalRows: baselineJournal.length + 1, @@ -365,7 +365,7 @@ describe("Tier WAC — Workspace effects across a process boundary", () => { const later = journal[1].rootId; expect(historical).not.toBe(later); - // The retained content the older root needs is no longer reachable from + // The saved content the older root needs is no longer reachable from // the live frontier, so restoration has to come from what that root // retains rather than from anything still live. const retired = manifestsOf(path, historical).filter( @@ -415,7 +415,7 @@ describe("Tier WAC — Workspace effects across a process boundary", () => { expect(committedAgain["currentRoot"]).toBe(first["currentRoot"]); expect(committedAgain["events"]).toEqual(first["events"]); - // Restoration published a root that was already retained; it created no + // Restoration published a root that was already saved; it created no // new one and lost no history. const after = committed(path); expect(after["roots"]).toBe(stored["roots"]); diff --git a/packages/workflow/tests/workspace-effect-loaded-copy.test.ts b/packages/workflow/tests/workspace-effect-loaded-copy.test.ts index acd80ca1f..e9281f1df 100644 --- a/packages/workflow/tests/workspace-effect-loaded-copy.test.ts +++ b/packages/workflow/tests/workspace-effect-loaded-copy.test.ts @@ -33,7 +33,7 @@ import { withWorkspaceCoordinationProvider, } from "../src/workspace/effect.ts"; import { createDurableWorkspaceOperation } from "../mod.ts"; -import { retainedWorkflowInstallation } from "../src/run.ts"; +import { savedWorkflowInstallation } from "../src/run.ts"; import { gitPlugin } from "../../git/src/plugin.ts"; import type { WorkflowRun } from "../src/run.ts"; import { GIT_HOST_EFFECT } from "../../git/src/git-host/effect.ts"; @@ -63,7 +63,7 @@ import type { * Asked of the Plugin value rather than assembled here, so what these cases * exercise is the same contribution a command gets — one Plugin value, and an * admission that derives this execution's identities from this execution's own - * retained history. + * saved history. */ function* gitPluginAdmissions(): Operation { const install = gitPlugin.install; @@ -155,7 +155,7 @@ const PERFORMED_COMPLETION: GitHostCompletion = Object.freeze({ /** * One run's history as another run's, so a fork can inherit it. * - * Only the Git-host record moves: its retained identity becomes the source's, + * Only the Git-host record moves: its saved identity becomes the source's, * and the durable operation is renamed to the digest that identity produces — * which is what the source itself wrote and what a fork copies verbatim. */ @@ -229,7 +229,7 @@ function loadedGitHostCopy(value: unknown): value is LoadedGitHostCopy { * module graph. * * The whole shared tree is copied rather than the four Git-host modules, - * because what the copy must reach — the retained identities and the canonical + * because what the copy must reach — the saved identities and the canonical * encoding beside them — is exactly what a real second copy reaches. The host * adapter is left behind: this boundary names no host, so a copy of it needs * none. What the copy does *not* carry is `@executablemd/workflow`: a second @@ -306,7 +306,7 @@ function useGitHostEffectComponent( function* gitHostDocument(stream: InMemoryStream): Operation { return yield* collect( yield* executeInstalled({ ...inlineSource(GIT_HOST_SOURCE), stream }, [ - retainedWorkflowInstallation(GIT_HOST_RUN), + savedWorkflowInstallation(GIT_HOST_RUN), yield* gitPluginAdmissions(), ]), ); @@ -529,12 +529,12 @@ describe("Tier DLG — physical Git-host package composition", () => { expect(gitHostYields(stream)).toHaveLength(1); }); - it("DLG3: the other copy consumes an inherited record under its retained identity", function* () { + it("DLG3: the other copy consumes an inherited record under its saved identity", function* () { // A fork's journal holds records its source wrote, and a Git-host effect is // named by a digest that includes the run id — so a copy that could not see - // the retained identity would fingerprint an inherited record with the fork + // the saved identity would fingerprint an inherited record with the fork // id and diverge. Which physical module object asks must not decide which - // retained history is accepted. + // saved history is accepted. const copy = yield* physicalGitHostCopy(); const sourceRunId = "run-297-git-host-source"; diff --git a/packages/workflow/tests/workspace-effect-transaction.test.ts b/packages/workflow/tests/workspace-effect-transaction.test.ts index 7754d5ec2..93e48dcb0 100644 --- a/packages/workflow/tests/workspace-effect-transaction.test.ts +++ b/packages/workflow/tests/workspace-effect-transaction.test.ts @@ -185,7 +185,7 @@ function asText(value: unknown): string | undefined { return typeof value === "string" ? value : undefined; } -function retainedRepositoryNames(path: string): string[] { +function savedRepositoryNames(path: string): string[] { const sqlite = new DatabaseSync(path); try { return sqlite @@ -246,7 +246,7 @@ function journalRoot(path: string, name: string): string | undefined { } } -function retainedRootCount(path: string): number { +function savedRootCount(path: string): number { const sqlite = new DatabaseSync(path); try { const value = sqlite.prepare("SELECT COUNT(*) AS count FROM workspace_roots").get()?.["count"]; @@ -745,7 +745,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { type: "yield", coroutineId: "root", description: { type: "workspace-proof", name: "replayed" }, - result: { status: "ok", value: "retained" }, + result: { status: "ok", value: "saved" }, }); let mutations = 0; function* workflow(): Workflow { @@ -913,7 +913,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const other = yield* createRun({ runId: "atomic-destination-other" }); const selectedPath = runPath(root, "atomic-destination-selected"); const baseline = yield* inspectWorkspace(selected, "/wrong-destination.txt"); - const roots = retainedRootCount(selectedPath); + const roots = savedRootCount(selectedPath); const copied: DurableStream = { readAll: () => selected.journal.readAll(), @@ -986,10 +986,10 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(mutations).toBe(0); expect(laterExecutions).toBe(0); // Refused before the transaction does any work: no savepoint opened, - // no root retained, no journal row committed, and the frontier is + // no root saved, no journal row committed, and the frontier is // where it was. expect(savepoints).toEqual([]); - expect(retainedRootCount(selectedPath)).toBe(roots); + expect(savedRootCount(selectedPath)).toBe(roots); expect(committedEventCount(selectedPath)).toBe(0); expect(yield* inspectWorkspace(selected, "/wrong-destination.txt")).toEqual(baseline); expect(yield* selected.journal.readAll()).toEqual([]); @@ -1016,7 +1016,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { function* (database) { selected = database; const baseline = yield* inspectWorkspace(database, "/post-publication.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let gateCalls = 0; let caught: unknown; let laterExecutions = 0; @@ -1050,7 +1050,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(gateCalls).toBe(1); expect(laterExecutions).toBe(0); expect(yield* inspectWorkspace(database, "/post-publication.txt")).toEqual(baseline); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); }, { @@ -1078,7 +1078,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { function* (database) { selected = database; const baseline = yield* inspectWorkspace(database, "/cancel-before-commit.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let gateCalls = 0; const guarded = trustedFilter(database.journal, function* (event) { if (event.type === "yield") { @@ -1099,7 +1099,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { yield* task.halt(); expect(gateCalls).toBe(1); expect(yield* inspectWorkspace(database, "/cancel-before-commit.txt")).toEqual(baseline); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); }, { @@ -1225,7 +1225,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { }); }); - it("WAC16: current-root publication failure rolls retained state back", function* () { + it("WAC16: current-root publication failure rolls saved state back", function* () { const root = yield* useStorageRoot(); yield* withStorage(root, function* () { @@ -1233,7 +1233,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const database = yield* createRun({ runId }); const path = runPath(root, runId); const baseline = yield* inspectWorkspace(database, "/current-root.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let caught: unknown; let laterExecutions = 0; tamper(path, (sqlite) => { @@ -1267,7 +1267,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(laterExecutions).toBe(0); tamper(path, (sqlite) => sqlite.exec("DROP TRIGGER refuse_current_root")); expect(yield* inspectWorkspace(database, "/current-root.txt")).toEqual(baseline); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); }); }); @@ -1330,7 +1330,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { observe, function* (database, connection) { const baseline = yield* inspectWorkspace(database, "/savepoint.txt"); - const roots = retainedRootCount(connection.path); + const roots = savedRootCount(connection.path); let caught: unknown; let laterExecutions = 0; observed.length = 0; @@ -1378,7 +1378,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(failure).toBeInstanceOf(Error); expect(laterExecutions).toBe(0); expect(yield* inspectWorkspace(database, "/savepoint.txt")).toEqual(baseline); - expect(retainedRootCount(connection.path)).toBe(roots); + expect(savedRootCount(connection.path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); if (phase === "create") { expect(observed.some((event) => event.kind === "create")).toBe(false); @@ -1458,7 +1458,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const database = yield* createRun({ runId }); const path = runPath(root, runId); const baseline = yield* inspectWorkspace(database, "/middleware-failure.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let replacementRuns = 0; let caught: unknown; let laterExecutions = 0; @@ -1521,7 +1521,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(failure).toBeInstanceOf(Error); expect(laterExecutions).toBe(0); expect(yield* inspectWorkspace(database, "/middleware-failure.txt")).toEqual(baseline); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); }); }); @@ -1540,7 +1540,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const database = yield* createRun({ runId }); const path = runPath(root, runId); const baseline = yield* inspectWorkspace(database, "/substituted.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let mutations = 0; function* workflow(): Workflow { yield* workspaceStep(database, "substituted-provider", function* (filesystem) { @@ -1563,7 +1563,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(mutations).toBe(0); expect(savepoints).toEqual([]); expect(yield* inspectWorkspace(database, "/substituted.txt")).toEqual(baseline); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); }); }); @@ -1576,7 +1576,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const database = yield* createRun({ runId }); const path = runPath(root, runId); const baseline = yield* inspectWorkspace(database, "/minimum.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); const observed: unknown[] = []; function* workflow(): Workflow { yield* workspaceStep(database, "minimum-publication", function* (filesystem) { @@ -1604,7 +1604,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(observed).toEqual([]); expect(committed.content).toBe("committed with Yield"); expect(committed.root).not.toBe(baseline.root); - expect(retainedRootCount(path)).toBe(roots + 1); + expect(savedRootCount(path)).toBe(roots + 1); expect(workspaceYields(events)).toHaveLength(1); expect(journalRoot(path, "minimum-publication")).toBe(committed.root); }); @@ -1618,7 +1618,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const database = yield* createRun({ runId }); const path = runPath(root, runId); const baseline = yield* inspectWorkspace(database, "/minimum-failure.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let collisions = 0; let caught: unknown; let laterExecutions = 0; @@ -1668,7 +1668,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(failure).toBeInstanceOf(Error); expect(laterExecutions).toBe(0); expect(yield* inspectWorkspace(database, "/minimum-failure.txt")).toEqual(baseline); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); expect(yield* database.journal.readAll()).toEqual([]); }); }); @@ -1712,7 +1712,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(committed.root).not.toBe(baseline.root); // The row and the bytes are published together, against the same root the // journal entry names. - expect(retainedRepositoryNames(path)).toEqual(["proof"]); + expect(savedRepositoryNames(path)).toEqual(["proof"]); expect(journalRoot(path, "retain")).toBe(committed.root); }); }); @@ -1725,7 +1725,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { const database = yield* createRun({ runId }); const path = runPath(root, runId); const baseline = yield* inspectWorkspace(database, "/kept/marker.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let caught: unknown; function* workflow(): Workflow { @@ -1754,8 +1754,8 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { // Nothing the attempt wrote survives: not the bytes, not the row, and not // a published root — and the failed Yield names the root it started from. expect(yield* inspectWorkspace(database, "/kept/marker.txt")).toEqual(baseline); - expect(retainedRepositoryNames(path)).toEqual([]); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRepositoryNames(path)).toEqual([]); + expect(savedRootCount(path)).toBe(roots); const events = workspaceYields(yield* database.journal.readAll()); expect(events).toHaveLength(1); expect(events[0]?.result.status).toBe("err"); @@ -1764,7 +1764,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { }); /** - * The window a retained view would otherwise still be inside. + * The window a saved view would otherwise still be inside. * * Not after the run — by then the transaction has ended and its own * authorization would refuse anyway, so a case that only looked there would @@ -1773,12 +1773,12 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { * its root is still open, which is exactly where "for this callback" has to * be a fact rather than a convention. */ - it("WAC29: every capability a mutation retained answers nothing once it returned", function* () { + it("WAC29: every capability a mutation saved answers nothing once it returned", function* () { const root = yield* useStorageRoot(); - const runId = "atomic-storage-retained"; + const runId = "atomic-storage-saved"; const path = join(root, `${runId}.sqlite`); let selected: WorkflowRunDatabase | undefined; - let retained: WorkflowWorkspaceTransaction | undefined; + let saved: WorkflowWorkspaceTransaction | undefined; /** * Operations built while the mutation was still valid. * @@ -1808,7 +1808,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { function* (transaction): Operation { members = Object.keys(transaction).sort(); storageMembers = Object.keys(transaction.storage).sort(); - retained = transaction; + saved = transaction; prepared = [ { name: "readTextFile", @@ -1824,7 +1824,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { operation: transaction.savepoint( // deno-lint-ignore require-yield (function* (): Operation { - throw new Error("the retained savepoint entered its body"); + throw new Error("the saved savepoint entered its body"); })(), ), }, @@ -1842,7 +1842,7 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { expect(storageMembers).toEqual(["all", "get", "run"]); expect(attempted).toBe(true); - // Every retained capability, synchronous and deferred alike. + // Every saved capability, synchronous and deferred alike. expect(refusals.map((refusal) => refusal.name).sort()).toEqual([ "all", "get", @@ -1861,13 +1861,13 @@ describe("Tier WAC — atomic provider-level Workspace effects", () => { }).toEqual({ name: refusal.name, held: true }); } // And nothing any of them attempted reached the run. - expect(retainedRepositoryNames(path)).toEqual([]); + expect(savedRepositoryNames(path)).toEqual([]); const settled = yield* inspectWorkspace(database, "/escaped.txt"); expect(settled.content).toBe(undefined); }, { *beforeCommit(candidate): Operation { - const transaction = retained; + const transaction = saved; if (candidate !== selected || transaction === undefined || attempted) { return; } @@ -1989,7 +1989,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { const path = runPath(root, runId); yield* retain(database, "current bytes", PROOF_REPOSITORY); const current = (yield* inspectWorkspace(database, "/kept.txt")).root; - const roots = retainedRootCount(path); + const roots = savedRootCount(path); let members: string[] = []; let storageMembers: string[] = []; @@ -2016,15 +2016,15 @@ describe("Tier WAR — read-only Workspace inspection", () => { // objects that have no writing member for a guard to be the only thing in // front of. expect(storageMembers).toEqual(["all", "get"]); - // Nothing moved: not the current root, and not the retained ones. + // Nothing moved: not the current root, and not the saved ones. expect((yield* inspectWorkspace(database, "/kept.txt")).root).toBe(current); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); }); }); - it("WAR2: reads a retained root and leaves the run on the one it was on", function* () { + it("WAR2: reads a saved root and leaves the run on the one it was on", function* () { const root = yield* useStorageRoot(); - const runId = "inspect-retained"; + const runId = "inspect-saved"; yield* withStorage(root, function* () { const database = yield* createRun({ runId }); @@ -2033,7 +2033,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { const current = yield* inspectWorkspace(database, "/kept.txt"); expect(current.content).toBe("the later bytes"); expect(current.root).not.toBe(earlier); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); const read = yield* readWorkflowWorkspace(database, { rootId: earlier }, (snapshot) => snapshot.filesystem.readTextFile("/kept.txt"), @@ -2047,7 +2047,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { // can observe the Workspace selected at the earlier root. expect(read.value).toBe("the earlier bytes"); expect(yield* inspectWorkspace(database, "/kept.txt")).toEqual(current); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); }); }); @@ -2072,9 +2072,9 @@ describe("Tier WAR — read-only Workspace inspection", () => { const path = runPath(root, runId); const earlier = yield* retainTwice(database, path); const current = yield* inspectWorkspace(database, "/kept.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); - // A failure inside the callback, after the retained root materialized. + // A failure inside the callback, after the saved root materialized. const raised = new Error("the inspection failed after materializing"); const failed = yield* readWorkflowWorkspace( database, @@ -2088,7 +2088,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { expect(failed.ok).toBe(false); expect(failed.ok ? undefined : failed.error).toBe(raised); expect(yield* inspectWorkspace(database, "/kept.txt")).toEqual(current); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); // And a cancellation, halted while the callback is suspended inside the // materialized root. @@ -2107,7 +2107,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { }); expect(yield* inspectWorkspace(database, "/kept.txt")).toEqual(current); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); // And the connection is still usable: a later inspection opens the run's // transaction again and reads the root the cancellation left it on, @@ -2122,9 +2122,9 @@ describe("Tier WAR — read-only Workspace inspection", () => { }); }); - it("WAR4: a retained snapshot answers nothing once its inspection returned", function* () { + it("WAR4: a saved snapshot answers nothing once its inspection returned", function* () { const root = yield* useStorageRoot(); - const runId = "inspect-retained-view"; + const runId = "inspect-saved-view"; yield* withStorage(root, function* () { const database = yield* createRun({ runId }); @@ -2165,7 +2165,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { const path = runPath(root, runId); yield* retain(database, "current bytes", PROOF_REPOSITORY); const current = yield* inspectWorkspace(database, "/kept.txt"); - const roots = retainedRootCount(path); + const roots = savedRootCount(path); // Built while the snapshot was valid, carried out of the callback, and // yielded afterwards. A gate checked only when the member is called would @@ -2195,7 +2195,7 @@ describe("Tier WAR — read-only Workspace inspection", () => { "valid only while the callback that received it is running", ); expect(yield* inspectWorkspace(database, "/kept.txt")).toEqual(current); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRootCount(path)).toBe(roots); }); }); @@ -2216,8 +2216,8 @@ describe("Tier WAR — read-only Workspace inspection", () => { const path = runPath(root, runId); yield* retain(database, "current bytes"); const current = yield* inspectWorkspace(database, "/kept.txt"); - const roots = retainedRootCount(path); - expect(retainedRepositoryNames(path)).toEqual([]); + const roots = savedRootCount(path); + expect(savedRepositoryNames(path)).toEqual([]); const attempts: { sql: string; refused: boolean }[] = []; const read = yield* readWorkflowWorkspace(database, {}, function* (snapshot) { @@ -2258,8 +2258,8 @@ describe("Tier WAR — read-only Workspace inspection", () => { expect(attempts).toHaveLength(6); expect(read.value).toEqual([]); // Nothing was written, nothing was dropped, and the run did not move. - expect(retainedRepositoryNames(path)).toEqual([]); - expect(retainedRootCount(path)).toBe(roots); + expect(savedRepositoryNames(path)).toEqual([]); + expect(savedRootCount(path)).toBe(roots); // And the connection is the one every other caller expects. This runs on // the run's own handle and opens a private transaction whose verification diff --git a/packages/workflow/tests/workspace-effect.test.ts b/packages/workflow/tests/workspace-effect.test.ts index 27ef572b5..3c6416c41 100644 --- a/packages/workflow/tests/workspace-effect.test.ts +++ b/packages/workflow/tests/workspace-effect.test.ts @@ -581,14 +581,14 @@ describe("Tier DLC — Workspace coordination selection", () => { } }); - it("DLC21: retained contextual continuation cannot reuse a completed invocation", function* () { + it("DLC21: saved contextual continuation cannot reuse a completed invocation", function* () { const stream = new InMemoryStream(); establishJournalProvenance(stream); const { provider, counts } = successfulProvider(); - let retained: ((request: unknown) => Operation) | undefined; - let retainedRequest: unknown; + let saved: ((request: unknown) => Operation) | undefined; + let savedRequest: unknown; function* workflow(): Workflow { - yield* workspaceStep("retained-continuation", function* () { + yield* workspaceStep("saved-continuation", function* () { return null; }); } @@ -596,20 +596,18 @@ describe("Tier DLC — Workspace coordination selection", () => { yield* scoped(function* () { yield* WorkspaceInvocationCollision.around({ *coordinate(args, next): Operation { - retained = (request) => next(request); - retainedRequest = args[0]; + saved = (request) => next(request); + savedRequest = args[0]; return yield* next(...args); }, }); yield* withWorkspaceCoordinationProvider(provider, durableRun(workflow, { stream })); }); - if (retained === undefined) { + if (saved === undefined) { throw new Error("the collision middleware did not retain its continuation"); } - expect(yield* raised(retained(retainedRequest))).toBeInstanceOf( - WorkspaceCoordinationProviderError, - ); + expect(yield* raised(saved(savedRequest))).toBeInstanceOf(WorkspaceCoordinationProviderError); expect(counts).toEqual({ providers: 1, executions: 1, publications: 1 }); expect(yieldEvents(stream.snapshot())).toHaveLength(1); }); diff --git a/packages/workflow/tests/workspace-files.test.ts b/packages/workflow/tests/workspace-files.test.ts index 2d57d46fd..2248f173e 100644 --- a/packages/workflow/tests/workspace-files.test.ts +++ b/packages/workflow/tests/workspace-files.test.ts @@ -1418,7 +1418,7 @@ describe("WF workflow document filesystem", () => { const recorded = yield* recordedFileEffects(database); const before = yield* database.journal.readAll(); - // Recreated through the private seam, without touching the retained + // Recreated through the private seam, without touching the saved // delete record. yield* mutateWorkspace(database, function* (workspace) { yield* workspace.filesystem.writeFile("/x.txt", "recreated"); @@ -1576,7 +1576,7 @@ describe("Tier WF — the run's own directories", () => { }); // WF23: the mutation, its outcome and the resulting root are one commit, and - // the content runs after it. Ordering is read off the retained effects, in + // the content runs after it. Ordering is read off the saved effects, in // the order they committed — the nested `` landing proves nothing on // its own, because a write creates its own parents recursively and would land // either way. @@ -1599,7 +1599,7 @@ describe("Tier WF — the run's own directories", () => { // // The nested `` existing proves nothing on its own: a write creates // its own parents recursively, so it would land whether or not `` - // had committed first. What settles the ordering is the retained order of + // had committed first. What settles the ordering is the saved order of // the effects themselves. const effects = yield* recordedFileEffects(database); const ensures = effects.filter((effect) => effect.name.startsWith("ensure-directory:")); @@ -1746,7 +1746,7 @@ describe("Tier WF — the run's own directories", () => { expect(replayed.output).toContain("INSIDE"); // No second ensure: the counter is unchanged. expect(made).toEqual(["/once"]); - // And the retained root is what a replay restores, not a new one. + // And the saved root is what a replay restores, not a new one. expect(rootOfLastEvent(path)).toEqual(after); }, { decorateFilesystem: countingMkdirs(made) }, diff --git a/packages/workflow/tests/workspace-root-restoration.test.ts b/packages/workflow/tests/workspace-root-restoration.test.ts index 9a11e4131..a4d9bceed 100644 --- a/packages/workflow/tests/workspace-root-restoration.test.ts +++ b/packages/workflow/tests/workspace-root-restoration.test.ts @@ -56,11 +56,11 @@ function* createCorruptionFixture(storage: string, runId: string): Operation 1_000); yield* capture(database, function* (workspace) { yield* workspace.filesystem.mkdir("/dir"); - yield* workspace.filesystem.writeFile("/dir/file.txt", "first retained bytes", 0o640); + yield* workspace.filesystem.writeFile("/dir/file.txt", "first saved bytes", 0o640); }); yield* setPrivateWorkspaceClock(database, () => 2_000); yield* capture(database, function* (workspace) { - yield* workspace.filesystem.writeFile("/dir/file.txt", "second retained bytes", 0o600); + yield* workspace.filesystem.writeFile("/dir/file.txt", "second saved bytes", 0o600); }); }); } @@ -128,7 +128,7 @@ describe("Tier WRR — private Workspace root restoration", () => { }); if (historical === undefined || later === undefined) { - throw new Error("the retained roots are unavailable"); + throw new Error("the saved roots are unavailable"); } const sqlite = new DatabaseSync(path); try { @@ -140,7 +140,7 @@ describe("Tier WRR — private Workspace root restoration", () => { .prepare("SELECT blob_hash FROM workspace_root_blob_refs WHERE root_id = ?") .get(historical.rootId)?.["blob_hash"]; if (!(historicalManifest instanceof Uint8Array) || !(historicalBlob instanceof Uint8Array)) { - throw new Error("the historical root has no retained content references"); + throw new Error("the historical root has no saved content references"); } expect( sqlite @@ -247,7 +247,7 @@ describe("Tier WRR — private Workspace root restoration", () => { } }); - it("WRR6: retained-root, content, topology, and live mismatches are read-only corruption", function* () { + it("WRR6: saved-root, content, topology, and live mismatches are read-only corruption", function* () { const storage = yield* useStorageRoot(); const cases: Array<{ runId: string; damage(database: DatabaseSync): void }> = [ { @@ -383,7 +383,7 @@ describe("Tier WRR — private Workspace root restoration", () => { } }); - it("WRR7: retained-root integer corruption is redacted, typed, and read-only", function* () { + it("WRR7: saved-root integer corruption is redacted, typed, and read-only", function* () { const storage = yield* useStorageRoot(); const cases: Array<{ runId: string; damage(database: DatabaseSync): void }> = [ { @@ -557,7 +557,7 @@ describe("Tier WRR — private Workspace root restoration", () => { yield* withStorage(storage, function* () { const database = yield* createRun({ runId: "rollback-cache" }); const baselineRoot = (yield* capture(database, function* (workspace) { - yield* workspace.filesystem.writeFile("/kept.txt", "known retained bytes"); + yield* workspace.filesystem.writeFile("/kept.txt", "known saved bytes"); })).rootId; yield* database.journal.append({ type: "close", @@ -585,7 +585,7 @@ describe("Tier WRR — private Workspace root restoration", () => { root: yield* workspace.currentRoot(), }; }); - expect(observed).toEqual({ content: "known retained bytes", root: baselineRoot }); + expect(observed).toEqual({ content: "known saved bytes", root: baselineRoot }); expect(yield* database.journal.readAll()).toEqual(baselineJournal); }); }); @@ -596,7 +596,7 @@ describe("Tier WRR — private Workspace root restoration", () => { yield* withStorage(storage, function* () { const database = yield* createRun({ runId: "cancel-cache" }); const baselineRoot = (yield* capture(database, function* (workspace) { - yield* workspace.filesystem.writeFile("/kept.txt", "known retained bytes"); + yield* workspace.filesystem.writeFile("/kept.txt", "known saved bytes"); })).rootId; yield* database.journal.append({ type: "close", @@ -629,12 +629,12 @@ describe("Tier WRR — private Workspace root restoration", () => { root: yield* workspace.currentRoot(), }; }); - expect(observed).toEqual({ content: "known retained bytes", root: baselineRoot }); + expect(observed).toEqual({ content: "known saved bytes", root: baselineRoot }); expect(yield* database.journal.readAll()).toEqual(baselineJournal); }); }); - it("WRR11: historical file sizes must agree with retained DOFS manifests", function* () { + it("WRR11: historical file sizes must agree with saved DOFS manifests", function* () { const storage = yield* useStorageRoot(); const path = runPath(storage, "historical-size"); let historicalRoot = ""; diff --git a/packages/workflow/tests/workspace-root.test.ts b/packages/workflow/tests/workspace-root.test.ts index 1b181b8d8..85a38175b 100644 --- a/packages/workflow/tests/workspace-root.test.ts +++ b/packages/workflow/tests/workspace-root.test.ts @@ -47,7 +47,7 @@ function parseRoot(root: StoredWorkspaceRoot): { return JSON.parse(root.manifest); } -describe("Tier WRR — immutable retained Workspace roots", () => { +describe("Tier WRR — immutable saved Workspace roots", () => { it("WRR1: the canonical empty root remains byte-for-byte compatible with schema v1", function* () { const storage = yield* useStorageRoot(); @@ -75,7 +75,7 @@ describe("Tier WRR — immutable retained Workspace roots", () => { } }); - it("WRR2: complete topology is canonical, content-addressed, and idempotently retained", function* () { + it("WRR2: complete topology is canonical, content-addressed, and idempotently saved", function* () { const storage = yield* useStorageRoot(); let first: StoredWorkspaceRoot | undefined; let independentlyBuilt: StoredWorkspaceRoot | undefined; @@ -88,7 +88,7 @@ describe("Tier WRR — immutable retained Workspace roots", () => { yield* workspace.filesystem.mkdir("/tree/nested", { mode: 0o700 }); yield* workspace.filesystem.writeFile( "/tree/nested/file.txt", - "retained-only-in-dofs-blobs", + "saved-only-in-dofs-blobs", 0o640, ); yield* workspace.filesystem.link("/tree/nested/file.txt", "/tree/hardlink.txt"); @@ -116,7 +116,7 @@ describe("Tier WRR — immutable retained Workspace roots", () => { yield* workspace.filesystem.mkdir("/tree/nested", { mode: 0o700 }); yield* workspace.filesystem.writeFile( "/tree/nested/file.txt", - "retained-only-in-dofs-blobs", + "saved-only-in-dofs-blobs", 0o640, ); yield* workspace.filesystem.link("/tree/nested/file.txt", "/tree/hardlink.txt"); @@ -147,7 +147,7 @@ describe("Tier WRR — immutable retained Workspace roots", () => { kind: "file", mode: 0o640, mtime: 1_700_000_000_000, - size: 27, + size: 24, manifest: first.manifestHashes[0], hardlink: "h0", }, @@ -162,13 +162,13 @@ describe("Tier WRR — immutable retained Workspace roots", () => { kind: "file", mode: 0o640, mtime: 1_700_000_000_000, - size: 27, + size: 24, manifest: first.manifestHashes[0], hardlink: "h0", }, ], }); - expect(first.manifest).not.toContain("retained-only-in-dofs-blobs"); + expect(first.manifest).not.toContain("saved-only-in-dofs-blobs"); const sqlite = new DatabaseSync(runPath(storage, "canonical-a")); try { diff --git a/packages/workflow/tests/workspace-transaction.test.ts b/packages/workflow/tests/workspace-transaction.test.ts index 5a76370b0..0b6ae3c25 100644 --- a/packages/workflow/tests/workspace-transaction.test.ts +++ b/packages/workflow/tests/workspace-transaction.test.ts @@ -342,28 +342,26 @@ describe("Tier WTX — unified WorkflowRun savepoints", () => { it("WTX10: savepoint rollback restores cache coherence before outer commit", function* () { const root = yield* useStorageRoot(); const runId = "savepoint-cache"; - let retainedRoot = ""; + let savedRoot = ""; yield* withStorage(root, function* () { const database = yield* createRun({ runId }); const initialized = yield* database.transact(function* (transaction) { return yield* withPrivateWorkspaceTransaction(database, transaction, function* (workspace) { - yield* workspace.filesystem.writeFile("/kept.txt", "known retained bytes"); + yield* workspace.filesystem.writeFile("/kept.txt", "known saved bytes"); const root = yield* workspace.capture({ publish: true }); - expect(yield* workspace.filesystem.readTextFile("/kept.txt")).toBe( - "known retained bytes", - ); + expect(yield* workspace.filesystem.readTextFile("/kept.txt")).toBe("known saved bytes"); return root.rootId; }); }); if (!initialized.ok) { throw initialized.error; } - retainedRoot = initialized.value; + savedRoot = initialized.value; const committed = yield* database.transact(function* (transaction) { yield* withPrivateWorkspaceTransaction(database, transaction, function* (workspace) { - expect(yield* workspace.currentRoot()).toBe(retainedRoot); + expect(yield* workspace.currentRoot()).toBe(savedRoot); const failure = yield* raised( savepoint( (function* () { @@ -376,10 +374,8 @@ describe("Tier WTX — unified WorkflowRun savepoints", () => { ), ); expect(failure).toBeInstanceOf(Error); - expect(yield* workspace.filesystem.readTextFile("/kept.txt")).toBe( - "known retained bytes", - ); - expect(yield* workspace.currentRoot()).toBe(retainedRoot); + expect(yield* workspace.filesystem.readTextFile("/kept.txt")).toBe("known saved bytes"); + expect(yield* workspace.currentRoot()).toBe(savedRoot); }); yield* transaction.journal.append(yielded("savepoint-cache-rollback")); }); @@ -398,7 +394,7 @@ describe("Tier WTX — unified WorkflowRun savepoints", () => { if (!next.ok) { throw next.error; } - expect(next.value).toEqual({ content: "known retained bytes", root: retainedRoot }); + expect(next.value).toEqual({ content: "known saved bytes", root: savedRoot }); expect(names(yield* database.journal.readAll())).toEqual(["savepoint-cache-rollback"]); }); @@ -408,7 +404,7 @@ describe("Tier WTX — unified WorkflowRun savepoints", () => { .prepare("SELECT workspace_root_id FROM journal_events ORDER BY sequence") .all(); expect(rows).toHaveLength(1); - expect(rows[0]?.["workspace_root_id"]).toBe(retainedRoot); + expect(rows[0]?.["workspace_root_id"]).toBe(savedRoot); } finally { database.close(); } diff --git a/packages/workflow/tests/xmd-artifact.test.ts b/packages/workflow/tests/xmd-artifact.test.ts index 53e7ce2ba..2660cfdd2 100644 --- a/packages/workflow/tests/xmd-artifact.test.ts +++ b/packages/workflow/tests/xmd-artifact.test.ts @@ -63,7 +63,7 @@ import { import { serializeDurableEvent } from "@executablemd/durable-streams"; import type { DurableEvent, Json } from "@executablemd/durable-streams"; import { SUSPENSION_ANSWER } from "../src/suspension/answer.ts"; -import type { RetainedDefinitionSources } from "../deno.ts"; +import type { SavedDefinitionSources } from "../deno.ts"; import { FIXTURE_BUNDLE_PATH, FIXTURE_BUNDLE_SOURCE, @@ -252,7 +252,7 @@ function forgedOwnWait(id: string): DetachedXmdArtifact { } /** - * The wait this run is stopped at, forged, and its retained row dropped. + * The wait this run is stopped at, forged, and its saved row dropped. * * Leaving the row would make this a row naming a request for another wait, * which a different check already answers. Removing it is what leaves the @@ -562,7 +562,7 @@ describe("XMD artifact container version 1", () => { expect(roomy.result.fileSha256).toBe(sha256Hex(yield* until(readFile(roomy.path)))); }); - it("C3 round-trips every retained record family", function* () { + it("C3 round-trips every saved record family", function* () { const directory = yield* useArtifactDirectory(); const contents = richArtifact(); const { path } = yield* sealed(directory, "evidence.xmd", contents); @@ -594,7 +594,7 @@ describe("XMD artifact container version 1", () => { contents.roots.map((root) => root.manifest).sort(), ); // A hardlink group, a symbolic link and a non-default mode are metadata a - // summary would drop, so each is asserted against the retained manifest. + // summary would drop, so each is asserted against the saved manifest. const current = read.roots.find( (root) => root.rootId === contents.frontier.currentWorkspaceRootId, ); @@ -872,7 +872,7 @@ describe("XMD artifact container version 1", () => { const read = yield* opened(path); const sealedFixture = richArtifact(); - // A provider checkpoint token is retained evidence rather than a capability + // A provider checkpoint token is saved evidence rather than a capability // over the host that issued it, so the ban is on the names that would carry // capability — and the two members that may be called `token` are named. const forbiddenNames = @@ -1050,7 +1050,7 @@ describe("XMD artifact container version 1", () => { content, ); }); - // Every way a retained answer can fail to be an answer to the history it + // Every way a saved answer can fail to be an answer to the history it // names. Each rewrites one member of one row and leaves the rest of the // artifact exactly as it was. const answerIdentity = (suspension: "consumed" | "pending"): string => @@ -1076,7 +1076,7 @@ describe("XMD artifact container version 1", () => { ...answer, requestEventId: "no-such-event", })); - // event-4 is the retained Git effect: a real event, and not a wait. + // event-4 is the saved Git effect: a real event, and not a wait. const suspensionNotARequest = yield* damageAnswer( "suspension-not-a-request.xmd", "pending", @@ -1112,7 +1112,7 @@ describe("XMD artifact container version 1", () => { return { ...rest, state: "pending" }; }, ); - // The publication side. Each rewrites the retained record of the answer + // The publication side. Each rewrites the saved record of the answer // event this run published, leaving the row that points at it untouched, so // what refuses them is the authentication of the publication itself. const damagePublication = (name: string, edit: (event: JsonObject) => JsonObject) => @@ -1135,7 +1135,7 @@ describe("XMD artifact container version 1", () => { })); const publicationPosition = yield* damagePublication("publication-position.xmd", (event) => ({ ...event, - // Another coroutine entirely, where the yield behind it is the retained + // Another coroutine entirely, where the yield behind it is the saved // Git effect rather than the request it claims to answer. coroutineId: "root.0", })); @@ -1221,7 +1221,7 @@ describe("XMD artifact container version 1", () => { "# A different document\n", ); }); - // A retained source the descriptor still maps a component name onto, + // A saved source the descriptor still maps a component name onto, // removed: the closure no longer carries what the definition names. const closureMembership = yield* damaged(path, join(directory, "membership.xmd"), (target) => { const identity = canonicalJsonText("workflows/Unused.md"); @@ -1281,7 +1281,7 @@ describe("XMD artifact container version 1", () => { // at both all agree. Nothing compares them to each other any more; what // refuses it is that no position in this run derives that identity. ["forged-answered.xmd", forgedOwnWait("f".repeat(32))], - // The same forgery on the wait this run is stopped at, with its retained + // The same forgery on the wait this run is stopped at, with its saved // row removed — so no answer row and no publication reaches the request, // and only holding the request itself to its position can catch it. ["forged-unanswered.xmd", forgedUnansweredWait("e".repeat(32))], @@ -1339,14 +1339,14 @@ describe("XMD artifact container version 1", () => { * * The container, its identity domain and its verifier are the ones C1–C9 * already hold; what is new is that a sealed artifact says, for every logical - * Agent session that contributed a retained Prompt, either how a fork would + * Agent session that contributed a saved Prompt, either how a fork would * continue it or exactly why nothing could. So these cases are about the two * profiles version 1 now has: that merged legacy artifacts still open and gain * nothing, that a finalized one is total, and that the confidential bytes it * carries stay bytes. */ describe("XMD artifact version 1 Agent portability evidence", () => { - it("F2 makes every Agent term and retained byte part of the identity", function* () { + it("F2 makes every Agent term and saved byte part of the identity", function* () { const base = finalizedArtifact(); const baseline = derivedIdentity(base); const portable = portableOf(base); @@ -1537,7 +1537,7 @@ describe("XMD artifact version 1 Agent portability evidence", () => { const cases: readonly (readonly [string, string, (database: DatabaseSync) => void])[] = [ [ - "nothing classified beside retained bundle bytes", + "nothing classified beside saved bundle bytes", "XmdArtifactInventoryError", (database) => { for (const key of [portableKey, incompleteKey, uncapturedKey]) { @@ -1551,7 +1551,7 @@ describe("XMD artifact version 1 Agent portability evidence", () => { (database) => dropRow(database, PORTABILITY_KIND, canonicalJsonText(incompleteKey)), ], [ - "a record for a session that retained no Prompt", + "a record for a session that saved no Prompt", "XmdArtifactInventoryError", (database) => insertRow( @@ -1563,7 +1563,7 @@ describe("XMD artifact version 1 Agent portability evidence", () => { ), ], [ - "bundle bytes for a session that retained no Prompt", + "bundle bytes for a session that saved no Prompt", "XmdArtifactInventoryError", (database) => insertRow(database, BUNDLE_KIND, idle.sessionKey, "bytes", encoder.encode("orphan")), @@ -1635,7 +1635,7 @@ describe("XMD artifact version 1 Agent portability evidence", () => { })), ], [ - "a retained Prompt whose session mapping is gone", + "a saved Prompt whose session mapping is gone", "XmdArtifactInventoryError", (database) => dropRow(database, "agent-session", canonicalJsonText(portableKey)), ], diff --git a/packages/workflow/vendor/cloudflare-computer-dofs/MANIFEST.json b/packages/workflow/vendor/cloudflare-computer-dofs/MANIFEST.json index fee352c25..4c9e1ff20 100644 --- a/packages/workflow/vendor/cloudflare-computer-dofs/MANIFEST.json +++ b/packages/workflow/vendor/cloudflare-computer-dofs/MANIFEST.json @@ -12,7 +12,7 @@ { "path": "PROVENANCE.md", "kind": "provenance", - "sha256": "1e3edf8e2b7298a498d9aca63571a833ee6726ef8101628db4af1e05dad7b04f" + "sha256": "49db4013ed8ec4d19ffefa48145f4d94c5c5b61b294d6e91e40fca767af8ee00" }, { "path": "generated/errors.d.ts", diff --git a/packages/workflow/vendor/cloudflare-computer-dofs/PROVENANCE.md b/packages/workflow/vendor/cloudflare-computer-dofs/PROVENANCE.md index a6adba04f..8870b6780 100644 --- a/packages/workflow/vendor/cloudflare-computer-dofs/PROVENANCE.md +++ b/packages/workflow/vendor/cloudflare-computer-dofs/PROVENANCE.md @@ -1,6 +1,6 @@ # Cloudflare Computer DOFS provenance -This directory contains the production source closure used by Executable Markdown's local retained Workspace provider. +This directory contains the production source closure used by Executable Markdown's local saved Workspace provider. - Repository: `https://github.com/cloudflare/computer` - Commit: `63d363632e558f7e077794988d36ed75017c2a62` diff --git a/plans/xmd-portable-artifact-quest.md b/plans/xmd-portable-artifact-quest.md index 3251afd88..ba6df15e7 100644 --- a/plans/xmd-portable-artifact-quest.md +++ b/plans/xmd-portable-artifact-quest.md @@ -14,7 +14,7 @@ Deliver one portable, immutable `.xmd` evidence artifact for a committed workflow-run frontier. CI and a local developer inspect the same bytes. A local -continuation always becomes a separately retained history fork; an artifact is +continuation always becomes a separately saved history fork; an artifact is never authority to resume, answer, cancel, delete, or otherwise advance the source run. @@ -33,16 +33,16 @@ contract in `specs/xmd-artifact-spec.md`; they do not change it. ## Current State -- The Deno workflow provider already has strict, read-only retained-run +- The Deno workflow provider already has strict, read-only saved-run inspection, recovery-copy handling, executor locks, history projection, and fork staging/commit boundaries in `packages/workflow/src/deno/lifecycle.ts`. - `packages/workflow/src/deno/fork-source.ts` and - `packages/workflow/src/deno/fork-write.ts` already copy a selected retained + `packages/workflow/src/deno/fork-write.ts` already copy a selected saved history prefix, Workspace roots, content blobs, Repository and Worktree rows into a new independent run. - `packages/cli/src/workflow.ts`, `packages/cli/src/workflow-management.ts`, and `packages/cli/src/workflow-fork.ts` own lifecycle grammar, presentation, and - fork preflight. They currently accept retained run IDs only. + fork preflight. They currently accept saved run IDs only. - `packages/cli/src/workflow-definition.ts` and `packages/cli/src/workflow-bundle.ts` establish and authenticate root and component Markdown from a pinned workflow definition. @@ -54,7 +54,7 @@ contract in `specs/xmd-artifact-spec.md`; they do not change it. - `.xmd`, artifact-format version, canonical manifest, and manifest identity are the public compatibility boundary. SQLite is only V1's private container encoding; raw schema and SQL are not public APIs. -- The artifact contains complete XMD-owned retained state plus the authenticated +- The artifact contains complete XMD-owned saved state plus the authenticated root/component Markdown source closure. It preserves all Workspace bytes, metadata, modes, links, and Repository/Worktree records exactly. - Credentials, retrieval metadata, host paths, locks, sidecars, open @@ -113,7 +113,7 @@ or a live database handle. - Define V1's distinct SQLite application marker, artifact/container versions, exact recognized schema, and canonical artifact manifest encoding. -- Define the canonical inventory of all retained semantic records and bytes; +- Define the canonical inventory of all saved semantic records and bytes; derive the lowercase SHA-256 artifact identity from the specified domain prefix and manifest bytes. - Write a sealed artifact from an already-complete in-memory snapshot, and open @@ -132,7 +132,7 @@ or a live database handle. 2. Re-encoding the same semantic snapshot with permitted physical SQLite differences yields the same canonical manifest and artifact identity, while the final-file digest remains a separate byte-transport value. -3. The reader verifies the complete container, all retained bytes, and the +3. The reader verifies the complete container, all saved bytes, and the derived identity before exposing status, history, forkability, Workspace, or definition data. 4. Foreign, live-run, malformed, future-version, and tampered artifacts refuse @@ -163,9 +163,9 @@ boundaries; do not teach the artifact container to acquire execution authority. - Add a provider-neutral lifecycle export request/result and Deno-provider implementation that takes the source executor lock, reads one committed - frontier, gathers all XMD-owned retained state, and hands a detached snapshot + frontier, gathers all XMD-owned saved state, and hands a detached snapshot to Story 1's writer. -- Obtain each root and bundled component source from retained bytes or +- Obtain each root and bundled component source from saved bytes or reauthorized retrieval, and authenticate it against the definition's object format, commit, path, and blob identity before sealing it. - Add `xmd workflow export --output=` with required local @@ -180,28 +180,28 @@ boundaries; do not teach the artifact container to acquire execution authority. 1. Export while another executor holds the source lock refuses and creates no target; an export holding the lock chooses one consistent committed frontier and releases the lock without changing source state. -2. A successful artifact contains the complete retained run state: run and prior +2. A successful artifact contains the complete saved run state: run and prior lineage, lifecycle/executions, filtered journal and provenance, every root, manifest/blob byte and filesystem metadata, Repository/Worktree rows, - suspension state, retained external-effect records, Agent mappings, and the + suspension state, saved external-effect records, Agent mappings, and the authenticated definition closure. 3. Missing retrieval authority or source objects, object-format/hash mismatch, - unreadable retained state, invalid output path, existing target, atomic + unreadable saved state, invalid output path, existing target, atomic publication failure, cancellation, or a temporary cleanup failure before publication leaves no requested output and does not claim export success. Successful atomic no-replace publication commits the export: cleanup of the private entry it published from is best effort, and a failure there is - reported as a retained leftover beside a successful export rather than + reported as a saved leftover beside a successful export rather than invalidating or rolling back the committed artifact. 4. A crashed source uses the existing recovery-copy discipline; export does not - repair, settle, replay, append to, or relabel the retained source. + repair, settle, replay, append to, or relabel the saved source. 5. CLI output distinguishes semantic artifact identity from final-file digest and its help warns that arbitrary Workspace content can be confidential. **Focused evidence:** Extend workflow artifact tests with a run containing multiple Workspace roots, file bytes/mode/symlink/hardlink state, Repository or Worktree records, a root plus an unexpanded bundled component, and a suspension -or retained effect. Verify that export includes the exact root/component bytes +or saved effect. Verify that export includes the exact root/component bytes and their declared object identities, and that mismatched or unavailable source refuses export. Repository-independent continuation from those bytes belongs to Story 4. Add CLI integration cases for successful atomic export, lock refusal, @@ -219,11 +219,11 @@ cover export. Story 3 may use Story 1 fixtures while it is implemented. **Ownership:** Lifecycle inspection projection and CLI grammar/presentation. -`list` remains retained-run discovery only. +`list` remains saved-run discovery only. **Implementation outcome:** -- Extend status/history source parsing so exactly one of a retained run ID or +- Extend status/history source parsing so exactly one of a saved run ID or `--artifact=` is required; reject ambiguous/missing source forms. - Project the validated artifact into the existing immutable status/history JSON shapes, adding artifact identity and artifact frontier while keeping any host @@ -236,7 +236,7 @@ Story 3 may use Story 1 fixtures while it is implemented. 1. `workflow status --artifact` and `workflow history --artifact`, including `--json` and `--forkable`, report the same semantic lifecycle/history fields - as an equivalent retained run plus artifact identity/frontier. + as an equivalent saved run plus artifact identity/frontier. 2. Every artifact inspection first completes Story 1 recognition; a malformed or tampered artifact returns no partial status/history row and remains byte-identical with no sidecars. @@ -251,7 +251,7 @@ Story 3 may use Story 1 fixtures while it is implemented. **Focused evidence:** Add CLI workflow-inspection cases that inspect one artifact from two paths (including a read-only directory), compare -retained-run/artifact JSON projections, prove no sidecars/mutations, and +saved-run/artifact JSON projections, prove no sidecars/mutations, and exercise grammar refusals. Run `deno task test packages/cli/tests/workflow-inspection.test.ts` plus the artifact reader tests. @@ -281,18 +281,18 @@ checkpoint can continue. - Extend fork lineage with artifact identity while retaining source run ID, selected event ID, and selected root ID. Preserve inherited-event provenance. - Treat artifact identity as an immutable fork-identity term when an explicit - destination run ID already exists. Compatible reuse requires the retained + destination run ID already exists. Compatible reuse requires the saved destination to name the same artifact identity as the new request, in addition to the existing source/checkpoint/root, definition, base, and props terms. **Acceptance criteria:** 1. Artifact fork grammar requires `--artifact` and `--at`, then creates a new - explicit or host-generated run ID; artifact and retained-run source forms are + explicit or host-generated run ID; artifact and saved-run source forms are mutually exclusive. The optional definition selects the embedded authenticated closure only when absent. 2. A fork obtains a new run ID and independent copied database/pages, roots, - retained bytes, and required definition state; deleting either source run or + saved bytes, and required definition state; deleting either source run or artifact cannot make the fork unreadable or resumably dependent on it. 3. Forkability uses the existing cumulative blocker vocabulary, and preflight refuses invalid artifact, non-checkpoint, blocked checkpoint, bad merged @@ -303,7 +303,7 @@ checkpoint can continue. provenance. 5. Forking never resumes or advances the artifact/source identity, never shares an executor lock or writable page, and leaves the artifact byte-identical. -6. A completed external effect replays only from retained evidence; an +6. A completed external effect replays only from saved evidence; an unavailable Agent/external continuation remains blocked and new live effects use the local host's authority and credentials. 7. Reusing an explicit destination run ID compares XMD artifact identity as a @@ -320,13 +320,13 @@ from artifact A under one explicit destination run ID, then request that same ID from artifact B with the same source run, checkpoint, selected root, definition, base, and props but a different artifact identity; prove the second request refuses before changing any destination row, journal event, Workspace root, or -retained byte. Run those explicit tests plus the artifact test file. +saved byte. Run those explicit tests plus the artifact test file. ## Delivery Order 1. Story 1 establishes the private encoding, public semantic identity, and verified immutable reader. -2. Story 2 makes a live retained run exportable through the authority-safe lock +2. Story 2 makes a live saved run exportable through the authority-safe lock and authenticated closure path. 3. Story 3 makes the verified reader observable through existing inspection commands. It can be developed in parallel with Story 2 after Story 1, but @@ -348,9 +348,9 @@ ships inspection; Story 4 ships artifact fork. | Definition closure is complete and authenticated at export | 2 | Exact root and unexpanded bundle-component bytes/object identities are sealed; mismatch or unavailability refuses | | Embedded definition closure is self-sufficient for continuation | 4 | Root and unexpanded bundle component fork after repository access is removed | | Workspace confidentiality is explicit | 2 | CLI help/output proves disclosure warning; artifact retains unfiltered Workspace fixture bytes | -| Artifact inspection equals retained evidence | 3 | Status/history JSON comparison after moving the exact file | +| Artifact inspection equals saved evidence | 3 | Status/history JSON comparison after moving the exact file | | Artifact never gains execution authority | 3, 4 | No artifact resume/control grammar; inspection trace has no execution/provider effects | -| Artifact fork creates independent lineage | 4 | Two forks from one file, retained artifact identity/provenance, source deletion and resumability | +| Artifact fork creates independent lineage | 4 | Two forks from one file, saved artifact identity/provenance, source deletion and resumability | | Explicit destination reuse includes artifact identity | 4 | Fork ID created from artifact A refuses artifact B with the same source/checkpoint/root before mutation | | Compatibility and forkability remain fail-closed | 4 | Blocked checkpoint and divergent candidate leave no discoverable destination | @@ -364,7 +364,7 @@ check, runtime, JSR, and required CI gates. Review this file against the structural checklist in `specs/xmd-artifact-spec.md` §7. In particular, verify that the story boundaries preserve: source-run authority; one committed locked frontier; total verified -read-only recognition; complete retained-state and definition closure; explicit +read-only recognition; complete saved-state and definition closure; explicit artifact lineage; artifact identity as an explicit destination fork-identity term; exclusion of credentials/provider state/host paths; exact Workspace preservation; and the `.xmd` semantic compatibility boundary. diff --git a/scripts/banned-words.js b/scripts/banned-words.js index d1c79a867..12163cf3f 100644 --- a/scripts/banned-words.js +++ b/scripts/banned-words.js @@ -10,4 +10,5 @@ export const BANNED_WORDS = [ "reclaims", "reclaimed", "reclaiming", + "retained", ]; diff --git a/scripts/bench-repl-file-dir.ts b/scripts/bench-repl-file-dir.ts index ae34d4a12..909a0b77f 100644 --- a/scripts/bench-repl-file-dir.ts +++ b/scripts/bench-repl-file-dir.ts @@ -62,13 +62,13 @@ import { useCompositionComponents } from "../packages/git/src/composition/instal import { useRunComposition } from "../packages/git/src/deno/run-composition/provider.ts"; import type { HelperAssembly } from "../packages/git/src/deno/composition/credential-helper.ts"; import { Execution } from "@executablemd/core/api"; -import { useReplRepositoryRecording } from "../packages/git/src/deno/retained-repository/provider.ts"; -import { admitReplRepositoryHistory } from "../packages/git/src/retained-repository/admission.ts"; +import { useReplRepositoryRecording } from "../packages/git/src/deno/saved-repository/provider.ts"; +import { admitReplRepositoryHistory } from "../packages/git/src/saved-repository/admission.ts"; import { denoRepositoryHost } from "../packages/git/src/deno/composition/host.ts"; import { REPL_REPOSITORY_LOCAL, REPL_REPOSITORY_SELECTION, -} from "../packages/git/src/retained-repository/records.ts"; +} from "../packages/git/src/saved-repository/records.ts"; /** What one invocation was asked to measure. */ interface Settings { @@ -342,14 +342,14 @@ function measure( checkout: string, managed: string, source: string, - retained: readonly DurableEvent[] = [], + saved: readonly DurableEvent[] = [], ): Operation { return scoped(function* (): Operation { const counted = counters(); // Every lock descriptor this run opens, observed at the one native function // `useAdvisoryLock` reaches for. The sidecar itself is left alone. const descriptors = yield* useObservedLocks(counted); - const stream = new InMemoryStream([...retained]); + const stream = new InMemoryStream([...saved]); const path = join(checkout, "entry.md"); yield* writeTextFile(path, source); yield* API.Env.around( @@ -407,16 +407,16 @@ function measure( }); yield* collect( yield* executeInstalled({ path, stream }, [ - { admissions: [(retained) => admitReplRepositoryHistory(retained)] }, + { admissions: [(saved) => admitReplRepositoryHistory(saved)] }, ]), ); } const ms = performance.now() - started; const events = yield* stream.readAll(); - counted.appends = events.length - retained.length; + counted.appends = events.length - saved.length; counted.localAppends = events - .slice(retained.length) + .slice(saved.length) .filter( (event) => event.type === "yield" && event.description.type === REPL_REPOSITORY_LOCAL, ).length; @@ -461,7 +461,7 @@ function report(name: string, samples: readonly Measured[]): void { console.log( `${"".padEnd(13)} git ${counted.commands} lock descriptors ${counted.lease} ` + `appends ${counted.appends} (local results ${counted.localAppends}) ` + - `retained records ${counted.records} journal ${counted.journalBytes} bytes`, + `saved records ${counted.records} journal ${counted.journalBytes} bytes`, ); } diff --git a/scripts/runtime-test-exclusions.ts b/scripts/runtime-test-exclusions.ts index 10f67676f..b16a462af 100644 --- a/scripts/runtime-test-exclusions.ts +++ b/scripts/runtime-test-exclusions.ts @@ -224,7 +224,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/git/tests/github-workflow-activation.test.ts", reason: - "assembles the real ordinary-run profile and executes a retained source-bundle run through the real Workflow host; both reach Workflow's storage adapter and so `node:sqlite` — absent from Bun and behind --experimental-sqlite on Node 22. What is lost is only the assembled-profile reading: the adapters' own activation ordering, and every refusal before configuration, are proven portably in github-activation.test.ts, which runs on all three runtimes", + "assembles the real ordinary-run profile and executes a saved source-bundle run through the real Workflow host; both reach Workflow's storage adapter and so `node:sqlite` — absent from Bun and behind --experimental-sqlite on Node 22. What is lost is only the assembled-profile reading: the adapters' own activation ordering, and every refusal before configuration, are proven portably in github-activation.test.ts, which runs on all three runtimes", issue: DERIVED_SCOPE, }, { @@ -260,7 +260,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/git/tests/repository-storage.test.ts", reason: - "clones local bare repositories with a real `git` into the Deno DOFS Workspace and reads the retained node:sqlite rows back; the provider is the Deno one by design", + "clones local bare repositories with a real `git` into the Deno DOFS Workspace and reads the saved node:sqlite rows back; the provider is the Deno one by design", issue: "https://github.com/taras/executable.md/issues/293", }, { @@ -272,7 +272,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/git/tests/repository-materialization.test.ts", reason: - "exports retained checkouts to host directories and links their roots at external clones, against a real node:sqlite WorkflowRun database", + "exports saved checkouts to host directories and links their roots at external clones, against a real node:sqlite WorkflowRun database", issue: "https://github.com/taras/executable.md/issues/293", }, { @@ -284,7 +284,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/git/tests/worktree-replay.test.ts", reason: - "substitutes a Worktree's retained checkout through the Deno DOFS Workspace and reads it back with a real `git`; node:sqlite remains behind --experimental-sqlite on Node 22", + "substitutes a Worktree's saved checkout through the Deno DOFS Workspace and reads it back with a real `git`; node:sqlite remains behind --experimental-sqlite on Node 22", issue: "https://github.com/taras/executable.md/issues/293", }, { @@ -356,7 +356,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/git/tests/pull-request-read.test.ts", reason: - "drives the three evidence reads against a real node:sqlite WorkflowRun database, the Deno DOFS Workspace adapter and a real `git` subprocess, and replays one from the retained journal; Bun has no node:sqlite at all and Node 22 keeps it behind --experimental-sqlite", + "drives the three evidence reads against a real node:sqlite WorkflowRun database, the Deno DOFS Workspace adapter and a real `git` subprocess, and replays one from the saved journal; Bun has no node:sqlite at all and Node 22 keeps it behind --experimental-sqlite", issue: "https://github.com/taras/executable.md/issues/576", }, { @@ -434,7 +434,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/cli/tests/workflow-retention.test.ts", reason: - "reads what `xmd workflow start` retained from its node:sqlite run store, which only the Deno entrypoints open; the portable half of the same contract is packages/cli/tests/process-retention.test.ts", + "reads what `xmd workflow start` saved from its node:sqlite run store, which only the Deno entrypoints open; the portable half of the same contract is packages/cli/tests/process-retention.test.ts", issue: "https://github.com/taras/executable.md/issues/366", }, { @@ -458,7 +458,7 @@ const DENO_ONLY_TOOLING: RuntimeExclusion[] = [ { path: "packages/workflow/tests/workflow-agent-checkpoints.test.ts", reason: - "publishes real Prompts into a real node:sqlite WorkflowRun database and reads the retained association back through the Deno storage adapter, including a legacy-shape fixture it edits with node:sqlite directly; Bun has no node:sqlite at all and Node 22 keeps it behind --experimental-sqlite", + "publishes real Prompts into a real node:sqlite WorkflowRun database and reads the saved association back through the Deno storage adapter, including a legacy-shape fixture it edits with node:sqlite directly; Bun has no node:sqlite at all and Node 22 keeps it behind --experimental-sqlite", issue: "https://github.com/taras/executable.md/issues/367", }, { @@ -677,13 +677,13 @@ const DENO_ONLY_REPOSITORY_PROVIDER: RuntimeExclusion[] = [ issue: DERIVED_SCOPE, }, { - path: "packages/git/tests/retained-repository-local.test.ts", + path: "packages/git/tests/saved-repository-local.test.ts", reason: - "the subject is a REPL entry's native repository host: it discovers a real checkout with a `git` subprocess and records what a real filesystem answered. The record format and the admission are provider-neutral and are proven on every runtime by packages/git/tests/retained-repository-records.test.ts", + "the subject is a REPL entry's native repository host: it discovers a real checkout with a `git` subprocess and records what a real filesystem answered. The record format and the admission are provider-neutral and are proven on every runtime by packages/git/tests/saved-repository-records.test.ts", issue: DERIVED_SCOPE, }, { - path: "packages/git/tests/retained-repository-lifecycle.test.ts", + path: "packages/git/tests/saved-repository-lifecycle.test.ts", reason: "holds a shared Git directory under a kernel-released exclusive advisory lock taken through the Deno runtime, and spawns a second Deno process to prove the cross-process half; Node and Bun expose no equivalent", issue: DERIVED_SCOPE, diff --git a/scripts/tests/plugin-compiled.test.ts b/scripts/tests/plugin-compiled.test.ts index a5b7b6a01..8581bf6f6 100644 --- a/scripts/tests/plugin-compiled.test.ts +++ b/scripts/tests/plugin-compiled.test.ts @@ -327,7 +327,7 @@ describe( yield* ensureDir(home); const document = path.join(dir, "release.md"); - yield* writeTextFile(document, "# Release\n\nretained by the run\n"); + yield* writeTextFile(document, "# Release\n\nsaved by the run\n"); const environment = { HOME: home, XMD_WORKFLOW_RUNS: runs }; const started = yield* timebox(TIMEOUT, function* () { @@ -341,7 +341,7 @@ describe( throw new Error("the compiled binary timed out starting a source-bundle run"); } expect(started.value.code).toBe(0); - expect(started.value.stdout).toContain("retained by the run"); + expect(started.value.stdout).toContain("saved by the run"); // The document is gone. The run is not. yield* rm(document, { force: true }); @@ -357,7 +357,7 @@ describe( throw new Error("the compiled binary timed out resuming a source-bundle run"); } expect(resumed.value.code).toBe(0); - expect(resumed.value.stdout).toContain("retained by the run"); + expect(resumed.value.stdout).toContain("saved by the run"); }); }); }, diff --git a/site/routes/docs/components.tsx b/site/routes/docs/components.tsx index d49f0b04c..d4fb7e6ec 100644 --- a/site/routes/docs/components.tsx +++ b/site/routes/docs/components.tsx @@ -379,7 +379,7 @@ export default define.page(function Components() {

Retaining also delays release rather than opting out of it. The invocation site is an ordinary scope, so when it finishes, fails, or is - cancelled, everything retained into it is torn down, innermost first. + cancelled, everything saved into it is torn down, innermost first. Nothing escapes structured concurrency and nothing needs an explicit release.

@@ -387,8 +387,8 @@ export default define.page(function Components() { retain() belongs to TypeScript components. An{" "} eval{" "} block is durable — a replay restores its values without running it — so - a resource retained from one would have nothing to re-establish it, and - the call is refused rather than silently succeeding. + a resource saved from one would have nothing to re-establish it, and the + call is refused rather than silently succeeding.

Scoped resources

diff --git a/site/routes/docs/index.tsx b/site/routes/docs/index.tsx index 6421980f0..fe11e499b 100644 --- a/site/routes/docs/index.tsx +++ b/site/routes/docs/index.tsx @@ -61,7 +61,7 @@ export default define.page(function GettingStarted({ url }) { - Runs a document in a retained Workspace, so an interrupted workflow + Runs a document in a saved Workspace, so an interrupted workflow resumes from its journal frontier instead of starting again. Unsupported operations fail explicitly instead of falling back to the host. Available through the Deno entrypoint and the compiled diff --git a/site/routes/docs/journal.tsx b/site/routes/docs/journal.tsx index d0eb9ab56..b11500a5d 100644 --- a/site/routes/docs/journal.tsx +++ b/site/routes/docs/journal.tsx @@ -92,7 +92,7 @@ export default define.page(function Journal() {

Replay does not rescan

- A replayed execution restores its retained output and history without + A replayed execution restores its saved output and history without scanning them again. The journal it replays already crossed the gate when it was written.

diff --git a/site/routes/index.tsx b/site/routes/index.tsx index 354ad1614..4c10abfcc 100644 --- a/site/routes/index.tsx +++ b/site/routes/index.tsx @@ -690,7 +690,7 @@ export default define.page(function Home({ url }) {

--journal{" "} writes a JSONL trace of what ran and where it stopped. It is - for this run, not retained workflow history. + for this run, not saved workflow history.

diff --git a/smoke-test/Thing.ts b/smoke-test/Thing.ts index 392d9a5c5..283b48904 100644 --- a/smoke-test/Thing.ts +++ b/smoke-test/Thing.ts @@ -5,7 +5,7 @@ * content this is a wrapper, so the resource belongs to the invocation and is * released when the wrapping finishes; written self-closing there is nothing * to wrap and the handle is for the caller to use afterwards, so the resource - * is retained at the invocation site instead. + * is saved at the invocation site instead. */ import { content, hasContent, retain } from "@executablemd/core"; diff --git a/specs/acp-client-spec.md b/specs/acp-client-spec.md index f9359c78f..fd2057e9f 100644 --- a/specs/acp-client-spec.md +++ b/specs/acp-client-spec.md @@ -4,7 +4,7 @@ The `Agent` Api, the `` / `` / `` components, prompt journaling and replay, the provider-factory seam, and the `Config` timeouts are implemented in `@executablemd/core`. A provider is supplied through the `rootProvider` factory seam described below. This specification owns the -public operations, configuration and retained record shapes. +public operations, configuration and saved record shapes. [Agent permissions and generated work](../architecture.md#agent-permissions-and-generated-xmd) own workflow isolation and mapping publication; [native Agent sessions](../architecture.md#native-agent-sessions) own the shared @@ -115,12 +115,12 @@ routes an opaque request and cannot manufacture that result. has neither. A host missing either refuses that agent before any provider effect, on the same terms as a missing coordinator. The observer is not a contextual Api for the same reason the coordinator is not: executable - validation decides which retained history may be accepted. + validation decides which saved history may be accepted. - While ownership is held and before any provider construction effect, the provider reconciles the route. A fresh `session()` places the session and publishes no route. The first subscribed consuming `prompt()` publishes or adopts `acp-first` before runtime creation, `ensureSession()` or a turn; an - established `session()` reconciles its retained route eagerly. An ensure that + established `session()` reconciles its saved route eagerly. An ensure that fails afterwards leaves that route standing. A launch by an adapter that names its own sessions publishes `client-native` under an identity that adapter allocated, unless existing durable ACPX state @@ -134,7 +134,7 @@ routes an opaque request and cannot manufacture that result. converts. - A `session()` or subscribed `prompt()` meeting a **bound** `client-native` route **attaches** to it. Under the same ownership it reobserves the build and - compares the binding exactly, requires any retained ACP arrangement to assert + compares the binding exactly, requires any saved ACP arrangement to assert that route's identity, selects the runtime for `(resolved agent command, binding)`, calls `ensureSession()` with `resumeSessionId` equal to that identity, and requires the returned canonical assertion to equal it before a @@ -508,7 +508,7 @@ Each prompt is one durable operation (`agent_prompt`). The record carries the prompt's identity and input, the agent and session identity, terminal status, stop reason, text (including partial text on failure), any structured failure, one optional canonical `configuration`, and one optional list of `permissions`. -The configuration is retained only +The configuration is saved only after the provider emits `started`: a turn that started is one the provider put under exactly those settings, while a configuration refusal started no turn and retains none. A prompt that starts and later fails or is cancelled retains @@ -566,7 +566,7 @@ specs/native-agent-session-launch-spec.md. A completed replay contacts no provider, so it neither reads nor applies any configuration. A successful prepared launch reapplies its one canonical -configuration to the exact retained Session before it continues; an +configuration to the exact saved Session before it continues; an unconfigured record performs no configuration read. Live prompt and launch records always derive their configuration from the final routed authentic use, never from a requested/effective pair. @@ -648,7 +648,7 @@ created with `mcpServers: []`, each session with `allowedTools: []`, and the permission mode is `deny-all` with non-interactive denial. A fixed session instruction layer states that boundary to the Agent in the same -terms, and states all of it: that no native tool is authorized; that retained +terms, and states all of it: that no native tool is authorized; that saved work can be asked for only when the prompt being answered asks for it and only in the exact closed shape that prompt supplies; and that nothing the Agent returns carries permission — source it writes is data this run may admit under @@ -686,7 +686,7 @@ Nothing else changes. `` is still exactly one turn, the Agent Api gains no workflow-only operation, and `xmd run` keeps its caller-selected cwd, its omitted MCP and session options, and its public permission routing. -#### Retained sessions +#### Saved sessions A provider session outlives one execution, so a continued run reattaches the conversation it was having rather than replaying its transcript into a new one. @@ -720,7 +720,7 @@ Continuation is decided before a turn starts: No refusal starts a replacement session. ACPX fixes a session's creation-time options when the ACP session is created and ignores them when it reuses a -persistent record, so continuing without comparing the retained policy would be +persistent record, so continuing without comparing the saved policy would be continuing a session created under a wider one. The mapping is a row in the run's own database, committed in the run's own @@ -989,13 +989,13 @@ none. still gets to choose how the session is constructed. Established says the route and the durable identity already exist, and `session()` keeps its eager behavior for one: it takes ownership where the agent needs it, reconciles the - retained route, ensures through the bound runtime, validates the exact identity + saved route, ensures through the bound runtime, validates the exact identity and host mapping, releases the handle, and starts no turn. Directory placement classifies a record still carrying the pending materialization marker as pending, and a record without one — including every record written before deferred materialization existed — as established. A - retained client-native route is established even before ACPX has attached to + saved client-native route is established even before ACPX has attached to it. A published `acp-first` route is not: it is what a first turn writes down before it runs, and a first turn nobody accepted leaves exactly that behind. @@ -1021,7 +1021,7 @@ none. `executablemd.session-materialization/v1`, carried as `{"state": "accepted"}` in the update metadata of a standard ACP `session_info_update`. It is control data: ACPX consumes it, and it is never appended to the conversation, published - as an event, exposed as prompt text or retained as a checkpoint token. Nothing + as an event, exposed as prompt text or saved as a checkpoint token. Nothing else promotes a record — not a returning `ensureSession()`, a `startTurn()` that returned, a synthesized `started` event, a first text delta, a terminal result, checkpoint metadata, an error code or a diagnostic. @@ -1103,7 +1103,7 @@ none. `ensureSession()` exactly as given; `permissions: "strict"` selects the workflow profile's permission path; and `sessions` replaces directory-walk placement with a host's own, answering with the placement's `state` as well as - its key and directory, and is where a retained session this host cannot + its key and directory, and is where a saved session this host cannot continue is refused. `sessions.established()` is called where an assertion exists and not before: after the ensure that validated a reattachment, and after the backend accepted a constructed session's first turn. Every one of diff --git a/specs/code-review-agent-spec.md b/specs/code-review-agent-spec.md index 4b5f33347..f285fc9b7 100644 --- a/specs/code-review-agent-spec.md +++ b/specs/code-review-agent-spec.md @@ -244,7 +244,7 @@ by path — and a command that names none has none of the forty-two names. One installation per command then serves every surface that describes or validates the vocabulary — ordinary execution, `xmd syntax`, structural validation, ``'s admission validator and `xmd plan`'s candidate validation -— so the three checks inside one Plan command consume one retained assembly +— so the three checks inside one Plan command consume one saved assembly rather than re-reading every packaged asset. --- diff --git a/specs/decisions.md b/specs/decisions.md index 2dbfab89c..3af86592d 100644 --- a/specs/decisions.md +++ b/specs/decisions.md @@ -713,7 +713,7 @@ that never gets there wedges the invocation the same silent way. ### Context -A retained `generated_xmd` admission holds the normalized policy it was granted +A saved `generated_xmd` admission holds the normalized policy it was granted under, and a continuation was compared against it whole and exactly — the Workspace roots and selected root included. But those two fields are values of the run's own mutable progress: every committed Workspace mutation retains @@ -733,10 +733,10 @@ here around an expansion that yields ordinary durable effects. ### Decision An admission's `roots` and `selectedRoot` remain exact as-of-admission -provenance in the retained record — no shape or migration change — and a -continuation checks them as a **retained basis, by membership**: every -admission root and the admission's selected root must still be retained, and -the current selected root must be retained, while additional roots and an +provenance in the saved record — no shape or migration change — and a +continuation checks them as a **saved basis, by membership**: every +admission root and the admission's selected root must still be saved, and +the current selected root must be saved, while additional roots and an advanced current root pass. Loss of any admission root refuses before generated work, with the unchanged fixed diagnostic. Every non-root term — class selection, selected pinned identities and forms, request ceilings — still @@ -755,7 +755,7 @@ preserves that distinction. Removing the bridge is DEC-034 conformance, **not** the continuation correction: the reproduced WGAC16 resume failed identically with and without it, and exact journal order does not distinguish the bridge, because a re-run -expansion re-offers the retained sequence either way. The observed defect was +expansion re-offers the saved sequence either way. The observed defect was the exact root comparison rejecting the run's own progress, and the membership basis is what fixes it. No durable-stream protocol, journal or record schema changes, and no child durable coroutine exists: replay evidence is GX18a–d and diff --git a/specs/executable-mdx-spec.md b/specs/executable-mdx-spec.md index 84f243feb..95ade837b 100644 --- a/specs/executable-mdx-spec.md +++ b/specs/executable-mdx-spec.md @@ -10,7 +10,7 @@ Use this specification for authored syntax, interfaces and conformance. Start with [a document running](../architecture.md#a-document-running) for ordinary -execution, retained workflows and replay; the [architecture reading map](../architecture.md#reading-this-architecture) +execution, saved workflows and replay; the [architecture reading map](../architecture.md#reading-this-architecture) routes implementation questions to their owning rules. An executable MDX document is a markdown file containing embedded JSX @@ -1082,7 +1082,7 @@ deno task build existing programmatic caller is unchanged. The path that starts a run states it; no shared runner infers it from whether a journal pathname was named. -| Invocation | Live routing | Retained result | +| Invocation | Live routing | Saved result | | --- | --- | --- | | `xmd run`, `xmd test` | as the table above | exit status only | | `xmd run --journal ` | unchanged | exit status, stdout, and stderr — diagnostic persistence of what the configured execution environment forwarded | @@ -1091,31 +1091,31 @@ no shared runner infers it from whether a journal pathname was named. `xmd run --journal` writes diagnostic history; `xmd run` has no command or option that reopens that file as continuation input. Runtime replay semantics -still govern a retained stream supplied programmatically — by an embedder, a -test, or a future retained execution host — but they do not imply a public CLI +still govern a saved stream supplied programmatically — by an embedder, a +test, or a future saved execution host — but they do not imply a public CLI resume UX. `xmd workflow resume` is the current public continuation command and -belongs to its separate retained workflow host. +belongs to its separate saved workflow host. A workflow names no journal — it owns one — and retains durably within the environment it authorized, because its process results are part of the run's -retained history: a resumed procedure reads back +saved history: a resumed procedure reads back what a command printed rather than running it again to find out. -Where nothing is retained the run never builds the complete strings: the Process +Where nothing is saved the run never builds the complete strings: the Process operation accumulates only when the caller asked it to. The exceptions are the two buffers a document asks for on its own account — a structural capture's, which has the capture's scope and holds received stdout alone, and a bound -block's, which has the expansion's and holds both received channels. Where output is retained it is kept in addition to the routing the +block's, which has the expansion's and holds both received channels. Where output is saved it is kept in addition to the routing the document chose — including for `silent` and captured blocks, because a display policy does not weaken an audit record. Silencing output for the host's terminal likewise leaves the record intact: not showing something and not knowing it are different decisions. -Retained output crosses the pre-persistence secret gate, which examines what the +Saved output crosses the pre-persistence secret gate, which examines what the boundary received — after enclosing middleware, not before. A host may therefore redact a credential upstream so that it never reaches the gate, and a host that introduces credential-shaped text upstream is refused like any other run that -would persist one. Output that is not retained is never accumulated for a +would persist one. Output that is not saved is never accumulated for a scanner to inspect. **Binding.** An ordinary `exec` block forwards both channels and raises on a @@ -1173,8 +1173,8 @@ request canonical execution issues and claims. The request retains the authored modifiers and the block context as they stood when it was issued. Middleware composes around the operation exactly as it does around an ordinary block — it may observe the request, refuse it by throwing, and delegate it — while -canonical execution authorizes and composes the retained chain, runs it against -the retained context, and reports the outcome to the expansion that issued the +canonical execution authorizes and composes the saved chain, runs it against +the saved context, and reports the outcome to the expansion that issued the request. The delegated modifier array is inspectable data and decides nothing, in-place edits to it after issue decide nothing, and the command a `codeBlock` handler answers with is not the command that runs. A request that was not issued @@ -1222,7 +1222,7 @@ nothing about the result. Each received channel decodes independently, so a character split across two chunks survives interleaving with the other channel. `Process.join()` may settle before the stdout and stderr pumps and their stdio middleware finish, so output a child writes as the pumps settle may not be -received at all and may therefore be missing from what is retained or captured. +received at all and may therefore be missing from what is saved or captured. effectionx #244 owns that limitation, and nothing here claims pump-complete tail delivery or retention until it is integrated. @@ -1329,7 +1329,7 @@ accepts. `silent` displays neither channel. A non-zero exit is still a failure: `silent` hides what the command printed, not whether it worked. It does not weaken a record either — with `--journal`, a silent block's stdout and stderr are -retained exactly. +saved exactly. **`silent timeout[30s] exec`** — `exec` journals the command result. `timeout` cancels the block if it overruns. `silent` displays nothing. The journal entry @@ -1731,15 +1731,15 @@ them against live names before constructing its durable effect. An invalid eval is document validation: it never becomes a `DurableEffect` and appends no eval `Yield`. A valid partial replay performs the same validation before yielding an effect, so a diagnostic from the original run leaves later recorded effects -aligned. If retained history instead contains a successful eval `Yield` that the +aligned. If saved history instead contains a successful eval `Yield` that the current definition no longer yields, the existing replay guard, divergence and stale-input rules reject that incompatible history; validation never consumes the recorded result or substitutes an unjournaled error. If collision handling terminates the document before another durable effect is reached, the durable root detects the unaligned eval `Yield` before writing `Close(err)`, raises `TerminalDivergenceError` with the collision as its cause, and leaves the -retained journal unchanged. Root and child termination apply this check to the -whole retained coroutine subtree, including completed children the current +saved journal unchanged. Root and child termination apply this check to the +whole saved coroutine subtree, including completed children the current definition never claimed. A compatible definition can still replay it. `ephemeral eval` validates its exports against durable names before execution; it may atomically @@ -1814,7 +1814,7 @@ Leaving an invocation runs one destructor with three ordered stages: 1. halt the content scope, to completion — everything projected content created; 2. halt the body — the resources the component itself acquired; -3. halt the invocation scope — whatever `persist` and `daemon` retained. +3. halt the invocation scope — whatever `persist` and `daemon` saved. Each stage finishes before the next begins, so projected content has stopped before the component releases anything of its own, whichever order the component @@ -1839,7 +1839,7 @@ Nested invocations tear down leaf-first: a component inside projected content has its own boundary beneath the content scope, so halting that scope dismantles the whole subtree first. Sibling invocations share nothing. -#### Retained resources +#### Saved resources A component whose result is only useful while something stays alive — a directory a later sibling still has to read, a server a later block still has @@ -1876,7 +1876,7 @@ returns its value; there is no accessor for either, and a component cannot inspect or install anything on its caller. Retaining delays release; it does not opt out of it. The site is an ordinary -scope, so a retained resource is torn down with it — leaf-first, on success, +scope, so a saved resource is torn down with it — leaf-first, on success, failure, and cancellation alike. Every invocation answers `retain()` for itself. One with no site to retain into @@ -1889,12 +1889,12 @@ scope with no relationship to the call site. `retain()` is an operation of TypeScript component execution. A component runs in full on every execution, which is what makes invocation-site lifetime -meaningful: each execution re-establishes what it retained, and each execution's +meaningful: each execution re-establishes what it saved, and each execution's site scope releases it. An eval block does not. Its execution is durable — a replay restores the block's exported values from the journal without entering the executor — so a block that -retained a resource would, on replay, produce a restored value naming a resource +saved a resource would, on replay, produce a restored value naming a resource nothing re-created. Eval execution therefore installs a provider that refuses `retain()`; a block that needs a resource to outlive it belongs in a TypeScript component. Making eval retention replay-safe is a durability question this @@ -1919,7 +1919,7 @@ processor. Instead: 2. `evalFactory` reads `persistent` after compiling the block 3. When true, only the **compiled VM block** (`fn(env.values)`) runs inside `evalScope.eval()` — not the entire modifier chain -4. Resources spawned during that execution are retained until the +4. Resources spawned during that execution are saved until the invocation's eval scope is destroyed, when the invocation completes ### 4.5 Eval journal entries @@ -1999,14 +1999,14 @@ run but are absent from the diagnostic trace. | `packages/cli/src/{deno,node,bun,compiled}.ts` | Entrypoints — each installs matching `API.Env` and `API.Service` adapters, then calls `runXmd` | | `packages/workflow/src/service-denial.ts` | `useWorkflowServiceDenial()`, the non-delegating provider `xmd workflow` installs in every start and resume scope | | `packages/cli/src/workflow.ts` | the runtime-neutral `xmd workflow` lifecycle: grammar, run opening, execution records, statuses and exit codes | -| `packages/cli/src/workflow-definition.ts` | establishing an immutable definition from a working-tree path, and loading a retained one again | +| `packages/cli/src/workflow-definition.ts` | establishing an immutable definition from a working-tree path, and loading a saved one again | | `packages/cli/src/deno-workflow.ts` | the Deno run store and Workspace attachment; Node and Bun install the refusing host instead | | `packages/workflow/src/deno/workspace/files.ts` | the transaction-bound `API.Files` provider — one durable Workspace effect per document read, write, directory ensure and search | | `packages/workflow/src/deno/workspace/host.ts` | `withWorkflowWorkspace()` — the run's effect coordinator, logical cwd `/`, and Files provider installed together inside one execution | | `packages/workflow/src/generated-observations.ts` | `evaluateGeneratedFragment()` — the workflow policy adapter over `evaluateGeneratedXmd()`, returning its rendered text directly as `Operation`; `GeneratedObservation` remains the read-table entry type, not an output envelope | | `packages/workflow/src/journal.ts` | the `workflow_run` record, canonical-record recognition, and the refusals that name differing fields without their values | -| `packages/workflow/src/run.ts` | `retainedWorkflowInstallation()` and `getWorkflowRun()` — associating one execution with a run already created, as the `ExecutionInstallation` a trusted host passes to `executeInstalled()`, contributing a mandatory run-identity admission and the `prepare` hook that reopens the run inside the durable root. The association resolves no revision and imports no Git feature, and a run retains the bytes it executes, so nothing afterwards reaches a repository for its Markdown | -| `packages/workflow/src/bundle.ts` | `workflowBundleInstallation()` — the `ExecutionInstallation` that closes one execution over a workflow's component bundle, carrying the pinned execution view and the admission that holds every retained component import to it | +| `packages/workflow/src/run.ts` | `savedWorkflowInstallation()` and `getWorkflowRun()` — associating one execution with a run already created, as the `ExecutionInstallation` a trusted host passes to `executeInstalled()`, contributing a mandatory run-identity admission and the `prepare` hook that reopens the run inside the durable root. The association resolves no revision and imports no Git feature, and a run retains the bytes it executes, so nothing afterwards reaches a repository for its Markdown | +| `packages/workflow/src/bundle.ts` | `workflowBundleInstallation()` — the `ExecutionInstallation` that closes one execution over a workflow's component bundle, carrying the pinned execution view and the admission that holds every saved component import to it | | `packages/cli/src/file-stream.ts` | `FileStream` — JSONL-backed `DurableStream` implementation | Dependencies: `@effectionx/scope-eval`, `@effectionx/timebox`, @@ -2583,7 +2583,7 @@ is ordinary recovery: not execute through *normal continuation*; structured concurrency guarantees ownership and teardown, it does not forbid error recovery. - recovery keeps the enclosing invocation alive. Work owned by the failed - projection unwinds with its scope, while resources deliberately retained to + projection unwinds with its scope, while resources deliberately saved to invocation lifetime stay owned by the invocation until it exits (§4.4). - a caught failure is finished with. The engine does not reassert it, and it never reaches the component's consumer boundary. @@ -2767,7 +2767,7 @@ It is **self-closing only** and declares one optional prop, `names`: a non-empty array of unique component-name strings. A paired spelling, any other prop, an empty list, a duplicate, a non-string member and a name this site has no component for are each refused before the symbols are read, so a refusal -produces no partial text and no successful retained result. +produces no partial text and no successful saved result. **The named form renders documentation.** `` renders each selected component's symbol metadata followed by the long-form @@ -2960,7 +2960,7 @@ the symbol origin kind `protected`, carrying the canonical core origin — never as a reserved registration, which is a *host's* claim under a name and can be absent, replaced or refused where this cannot. A workflow-bundle member is reported under the kind `workflow`, carrying its canonical logical path **and** -the retained source's own hash, so it stays distinguishable from a repository +the saved source's own hash, so it stays distinguishable from a repository candidate, which is whatever that path holds now. Both kinds are additions, so the symbols are **version 2**: a version-1 reader was promised a closed set of origins, and neither emitting an unknown kind nor reusing a @@ -2982,7 +2982,7 @@ durable boundary is rebuilt without its class and without any non-enumerable property, so a refusal recorded as a failure would come back on replay meaning less than it meant live. -The component interprets that value only after publication succeeds. A retained +The component interprets that value only after publication succeeds. A saved refusal is therefore classified identically on a live run and on a replay, and a publication failure — the append, the journal, the secret gate — prevents interpretation entirely and remains terminal rather than becoming a refusal of @@ -3018,7 +3018,7 @@ one of them. The bundle tier exists only inside a workflow run, and such a run searches no repository directories at all — so a declared name resolves to the exact -retained source and to nothing beside it in a mutable checkout. Core's +saved source and to nothing beside it in a mutable checkout. Core's own components stay available underneath, and a bundle may claim neither structural syntax, a name core supplies, nor a name the host reserved: those collisions are refused before the root document is imported. Ordinary @@ -3116,7 +3116,7 @@ not from a component the declaration imported, and not from middleware. **The offer is what admits the import, and it is spent where it was made.** What a private import may invoke is the object canonical core's own resolver produced *inside -that ask*. An answer retained from another import authorizes nothing, however +that ask*. An answer saved from another import authorizes nothing, however exactly it describes the same private component: eligibility belongs to the authored occurrence, not to the name, so a handler that delegates a legitimate private import and keeps what came back cannot hand it to a later site. @@ -3479,7 +3479,7 @@ observation and delegation are unchanged. **Which imports are closed is a property of the name, not of the execution.** A component bundle closes every import a workflow run makes, including one that -resolved to a core default underneath it: a run is a run *of* those retained +resolved to a core default underneath it: a run is a run *of* those saved sources, and a name resolving outside it is what the bundle exists to prevent. A declared Markdown component closes only the names it declares — the component itself and @@ -4146,7 +4146,7 @@ function* durableImportComponent( A component the workflow definition is closed over records its own shape. The path it records is the one that definition retains it under — a logical path -inside the bundle — so an authored position inside a retained component names +inside the bundle — so an authored position inside a saved component names it by the same path the run does: ```json @@ -4183,13 +4183,13 @@ captures both *which file was found* (path) and *what was in it* (content); a registration entry captures the origin that named it, because a function cannot be serialized and the implementation is looked up again on replay. A workflow entry captures the canonical logical path — never the `./Name.md` a root -document wrote — the source's own hash and the exact retained source. Replay +document wrote — the source's own hash and the exact saved source. Replay reconstructs the component from its own record without resolving a name or reading a file. Nothing else is added: no checkout path, locator, timestamp, provider or generated identity. The trusted host that installed the bundle contributes a journal admission that -holds every retained component import to it before public document policy or the +holds every saved component import to it before public document policy or the root import. A recorded import that names a component the bundle does not declare, records a declared name as anything other than a bundled component, holds a different path, hash or source, or is a repository selection other than @@ -4401,10 +4401,10 @@ exposes no body. Names, structural copies and independent wrappers confer no permission. Neither route nor projection operations appear on public host, profile, document, middleware or generated-request inputs. Another loaded copy cannot observe or inject them. Routes and descendants close with their owner; -retained operations fail after closure, and one execution cannot authorize +saved operations fail after closure, and one execution cannot authorize another. Durable admission retains existing stable identities, forms, source, profile inputs and results only. Replay reconstructs callable routing from the -current execution's verified answers, never from a retained function or route. +current execution's verified answers, never from a saved function or route. **`allow` narrows; it never grants.** It names an effect *class* — `read` or `write` — and the class resolves to the selected spellings whose owners offer @@ -4495,7 +4495,7 @@ What stays contextual is the interaction and only the interaction. The pinned body reaches the Elicitation Api lexically in scope where the fragment runs, so the host, an enclosing `` region or the established missing-provider refusal owns how a person is asked — exactly as for an authored ``. Its -validated answer is retained by the ordinary `elicit` durable operation, may drive +validated answer is saved by the ordinary `elicit` durable operation, may drive a following write through ordinary bindings, and is restored on replay without contacting a provider. The `generated_xmd` record keeps the identity the admitted spelling runs under, the spelling itself, and nothing about the answer. @@ -4551,13 +4551,13 @@ an ordinary captured read reporting `Err`. It is not on a missing, duplicate, revoked or malformed profile; a missing or broken protected route or Syntax reference; a Files provider that throws or answers with malformed infrastructure data rather than an ordinary `Err`; durability divergence, stale source or -permission, or unreadable retained data; persistence, journal or +permission, or unreadable saved data; persistence, journal or secret-publication failure; unexpected runtime failure; teardown failure; or outer cancellation. The tag is per throw site rather than per error class, so a `GeneratedXmdError` is not by itself such a marker and no consumer may read one by matching its message. -**A refusal that must survive replay is retained as a value, not as an error.** A +**A refusal that must survive replay is saved as a value, not as an error.** A failure crossing a durable boundary is rebuilt without its class and without any non-enumerable property, so a classification applied to the error is lost on replay. Where a refusal is recorded — canonical `` selection is the case @@ -4622,9 +4622,9 @@ An admission is therefore addressed by the element it was made for rather than taken in the order preflight recorded it. An untaken alternative and a zero-iteration body consume nothing; repeated invocations of one authored element reuse that element's own admitted identity and form; and two elements -sharing a name cannot take one another's admissions. The retained record is +sharing a name cannot take one another's admissions. The saved record is unchanged — a continuation re-derives the addressing by preflighting the -retained source, which yields the same elements in the same places. +saved source, which yields the same elements in the same places. Binding ownership is stated per region. A component's children do not escape to later siblings, and a component's own `as` becomes visible only to later @@ -4654,7 +4654,7 @@ construct that gives them meaning, so reaching one directly is a stray element, and `` requires a lexically enclosing ``. Admission records are unchanged. Constructs keep their exact spelling and -nesting in the retained source, never join the retained list of named component +nesting in the saved source, never join the saved list of named component identities, and the record version is unchanged. A historical refusal remains a refusal on replay; only a fresh invocation obtains the new admission decision. @@ -4662,7 +4662,7 @@ Generated expression props are declarative data expressions. Their recursive grammar admits null, string and boolean literals; finite JSON numbers; identifiers naming current fragment-local bindings; arrays; objects; and object shorthand for a binding. A JSON number may include a leading minus directly -attached to its numeric literal. Core recognizes that form from the retained +attached to its numeric literal. Core recognizes that form from the saved authored expression and requires the resulting number to be finite, so `-1` is data while `1e999` and `-1e999` refuse. The leading minus is part of the admitted numeric grammar, not general unary evaluation: `+1`, `-note`, `!note`, @@ -4676,7 +4676,7 @@ ordinary trusted-document `new Function()` path. An unbound identifier fails as an ordinary binding error before the consuming component runs. The rule belongs to generated data composition, not to Json. -**Each Evaluate occurrence owns its retained work.** Its admission and effects +**Each Evaluate occurrence owns its saved work.** Its admission and effects cannot be consumed by another occurrence or reassigned through caller-controlled state. Completed effects are ordinary history as soon as they commit; a later refusal, failure or cancellation does not roll them back. Partial continuation @@ -4751,7 +4751,7 @@ canonical path of heading labels that reaches it. Selecting one executes: 2. the direct content of every ancestor needed to reach the target; and 3. the selected heading's complete subtree. -Sibling subtrees do not execute. Retained headings stay in the projected body, +Sibling subtrees do not execute. Saved headings stay in the projected body, so the projection reads as a document rather than as an excerpt. ##### Which headings are targets @@ -4774,7 +4774,7 @@ present in the root flow, which need not be `h1`. When the document has exactly one outermost heading, that heading is the document **title**: it takes no level in any target path, it is no target -itself, and its heading and direct content are retained in every projection +itself, and its heading and direct content are saved in every projection beneath it. When the document has more than one outermost heading, each of them takes a path level. A document with no addressable heading has an empty catalog. @@ -4792,7 +4792,7 @@ addressable. A label is the statically rendered Markdown text of the heading: formatting and link destinations are removed, while visible text, inline-code text, and image -alternative text are retained. The result is normalized to NFC, every run of +alternative text are saved. The result is normalized to NFC, every run of Unicode whitespace collapses to one ASCII space, leading and trailing whitespace is trimmed, and case is preserved. There are no generated slugs, suffixes, case folding, or punctuation removal. @@ -4863,15 +4863,15 @@ Source ranges are defined against the original, unprojected body: - the **selected subtree** runs from the selected heading's start to the next heading of equal or smaller depth, or to the body end. -The projected body is the preamble, each retained ancestor's direct content in +The projected body is the preamble, each saved ancestor's direct content in order, and the selected subtree. For a sole outermost title, the title is the -first retained ancestor even though it takes no level in the path. +first saved ancestor even though it takes no level in the path. -Each retained range is scanned separately, under the origin that range has in +Each saved range is scanned separately, under the origin that range has in the original file — its path, its offset, and its line. The ranges are not concatenated and rescanned: skipped source must not renumber what follows it, -because a retained element's source position is what its expansion identifier -is derived from. A retained element therefore carries the same expansion ID in +because a saved element's source position is what its expansion identifier +is derived from. A saved element therefore carries the same expansion ID in a targeted run as in a full one, and two targets that retain it agree with each other. The target string takes no part in expansion identity; a run's own identity is what distinguishes the effects of two target runs. @@ -4879,7 +4879,7 @@ identity is what distinguishes the effects of two target runs. Frontmatter, root props, `returns`, the return mode, and `` behavior are unchanged and apply to the projected body. Structural validation applies to the projected body too: an invalid skipped sibling is irrelevant, while an invalid -retained range fails before any authored effect in the projection runs. +saved range fails before any authored effect in the projection runs. ##### Failure timing and durable identity @@ -4895,8 +4895,8 @@ target is workflow-definition identity, and identity may not be decided by anything a document, a component, or an enclosing scope can replace — a public replay guard installed further out may decline to delegate, which is what composable policy is for. The check therefore lives inside the journal the -execution hands to `durableRun`: it reads the retained history once, owns the -retained snapshot every later phase observes, validates the recorded selection +execution hands to `durableRun`: it reads the saved history once, owns the +saved snapshot every later phase observes, validates the recorded selection and the required root-import structure against it, and passes that same snapshot on. It is also a trusted journal-provenance wrapping site. Provenance is not transitive, so a wrapper is unproven unless its wrapping site carries the source @@ -4906,27 +4906,27 @@ before any document code exists and it delegates every append to the exact stream it was handed — and it transfers only the witness that source already has, establishing none. -It runs ahead of public guard policy, of any retained event reaching execution, -of a retained terminal result being reused, of authored work, and of any append. +It runs ahead of public guard policy, of any saved event reaching execution, +of a saved terminal result being reused, of authored work, and of any append. The snapshot it owns covers **every** event that participates in admission, indexing, or terminal reuse — a recorded completion as much as a recorded effect — so a completion cannot belong to one coroutine while admission asks and to another while the run reuses it, and cannot carry one result while admission accepts it and another while the run consumes it. A recorded completion's result -is detached as the history is retained, and recognized during admission, so +is detached as the history is saved, and recognized during admission, so nothing the backend still owns reaches a later phase. That snapshot is also immutable to public policy. Guard `check`, `admit`, and -`decide` receive an isolated copy rather than the retained events, so a handler +`decide` receive an isolated copy rather than the saved events, so a handler cannot rewrite a recorded target, the recorded content, a selection failure, an effect description, or a result after admission accepted it. What a document finally receives is a third thing again: a fresh mutable copy taken from the -retained history at consumption, so a resumed binding is still ordinary JSON its own +saved history at consumption, so a resumed binding is still ordinary JSON its own continuation writes to. Public `ReplayGuard` policy remains composable and may short-circuit other public guards; it cannot suppress this. Reusing a recorded terminal result additionally requires exactly one -recognizable root import in the retained history, belonging to the coroutine +recognizable root import in the saved history, belonging to the coroutine whose terminal result is being reused. Reusing that result means standing behind the selection its root import established; a history that recorded none, recorded two, or recorded one belonging to another coroutine establishes nothing @@ -5043,7 +5043,7 @@ function isCanonicalDocumentTarget(target: string): boolean; canonical target by the round-trip rule above: it is definitive on that question, so a consumer outside this package validates a target it holds by asking rather than by restating the rule. A stored workflow definition uses it -on the exact target it retained, because identity two packages define separately +on the exact target it saved, because identity two packages define separately is identity they can disagree about. It takes no leading `#` — that delimiter belongs to a document reference, not to a target. @@ -5106,7 +5106,7 @@ terminal result can be reused. The same failing selector replays its own failure; any difference in outcome, including a different selector that fails the same way, is stale input. No authored effect runs in either case. -The recorded selector is sanitized invocation metadata, retained only so an +The recorded selector is sanitized invocation metadata, saved only so an ordinary failed execution can be reproduced. It never occupies the exact-target field and never reaches a workflow definition. @@ -5166,7 +5166,7 @@ a journal is pushed to by the iteration that resumes on it. "Read once" spans the phases, not one accessor. The guard validates a recorded selection and the replay path then projects it, and those observe one settled -value: the check phase is handed the retained Yields rather than the stream's +value: the check phase is handed the saved Yields rather than the stream's own events, and the stream is consulted at most once. Otherwise a source that answered differently between validation and consumption would have the guard approve one recorded target while the run executed another — exact-target @@ -5183,14 +5183,14 @@ A root import whose recorded result is not `ok` is left alone: a root can fail for reasons that are not about selection, and those failures are not this protocol's to interpret. -A replay that reuses **retained terminal history** must first establish exactly +A replay that reuses **saved terminal history** must first establish exactly one recognizable root import. Reusing a recorded terminal result means standing behind the selection its root import established, and a history that recorded none — or recorded two — establishes nothing to stand behind; the per-event check has nothing to object to, so the refusal belongs to the history as a whole. A missing, duplicated, or unreadable required root import fails with the same fixed cause-free diagnostic, executes no authored work, appends nothing, -and never returns the retained terminal result. A journal with no retained +and never returns the saved terminal result. A journal with no saved terminal result is unaffected: it replays what it has and continues live, so a root import it does not contain is one this run performs. @@ -5220,7 +5220,7 @@ encoding nor label normalization applies to prose. **A description describes; it never identifies.** It takes no part in a target path, a selector match, a projection, an execution, failure data, a workflow -definition identity, retained history, or a replay decision. +definition identity, saved history, or a replay decision. ##### The command line addresses a document @@ -5249,7 +5249,7 @@ Targets in README.md ``` Each row is `formatDocumentReference(path, target)`, in source order, duplicates -retained, so a duplicate canonical path appears twice with its own description. +saved, so a duplicate canonical path appears twice with its own description. A section that states no description is listed all the same, because it is still selectable. There is no unqualified whole-document row. A targetless file, an inline document and a document read from standard input have no target section at all — neither @@ -5410,7 +5410,7 @@ invocation-directory resolution, and `#` as document text — are owned by the checked-in Markdown suite at `packages/cli/tests/document-suites/inline/Inline.test.md`, whose children run through the production run host; the tier launcher runs it once per runtime -corpus. The retained IE rows in `inline-cli.test.ts` prove argv, environment, +corpus. The saved IE rows in `inline-cli.test.ts` prove argv, environment, help, value-root process routing, and that inline execution adds no top-level filesystem entry — a file, a directory, or a symlink — which only `readdir()` observes in every entry kind. @@ -5709,7 +5709,7 @@ dispatcher admits. It admits a call when every one of these holds: kept from a sibling, from a finished element, or from a concurrent invocation of the same component answers nothing; - canonical resolution selected **this** dispatcher for the import that led - here, so a dispatcher a handler retained and answered with reaches no body. + here, so a dispatcher a handler saved and answered with reaches no body. Otherwise it raises the refusal the component declared — the component's own error, so moving the decision ahead of the body does not change whose failure it @@ -5961,7 +5961,7 @@ above, so per-segment expansion produces stable diagnostic identifiers. Where a component (or the root) declares ``, this same rewriting drives each of its regions, but only the content of declared output regions is -retained; content outside them executes for its effects without rendering, and +saved; content outside them executes for its effects without rendering, and the first error produced while executing that non-rendered documentation stops the body immediately (§6.9). @@ -6545,7 +6545,7 @@ under its own contract and is unaffected (§5.3). Nothing durable records the absence. Prop resolution writes no journal entry, so a replay expands the invocation again and reconstructs the same validated prop -set, and no resolved prop, validated prop, retained event or restored prop holds +set, and no resolved prop, validated prop, saved event or restored prop holds `undefined`. A **capture** is the exception. A registration (§5.3) may declare props the @@ -6924,7 +6924,7 @@ terms as ``. **Neither construct is durable state.** They append no journal event. A partial replay rebuilds the selection through ordinary expansion and restores completed effects in the chosen case from their own records, continuing live at the next -effect; a completed replay returns the retained result without expanding +effect; a completed replay returns the saved result without expanding anything. #### `` bounded repetition directive @@ -7171,7 +7171,7 @@ the ``, in an environment of its own, with its own eval scope. A binding it makes, a binding it rebinds and a resource it retains stay available to that child's own later work, and reach neither a sibling nor the work after ``. Children do not communicate through bindings, and `` takes no -`as` and produces no value. A retained resource is released when its child ends. +`as` and produces no value. A saved resource is released when its child ends. **Output.** Each child expands into a private buffer. After every child succeeds, `` emits their renderings in authored order; before that it emits @@ -7193,7 +7193,7 @@ join — a close holding only text could not say what its bytes were, and a replayed child's source would be published as prose. A complete replay runs no spawned work and rebuilds the output from those closes; a partial replay restores the children that closed and resumes only the unrecorded ones, under -the same identities. A retained close that does not read as that exact shape — +the same identities. A saved close that does not read as that exact shape — not a list, a member that is not a run, a run missing or exceeding its two members — fails the run rather than contributing part of a document. Inserting, removing or reordering spawns is a definition change and follows the ordinary divergence contract. @@ -7203,7 +7203,7 @@ fails the ``, cancels every unfinished sibling and waits for all of them to finish tearing down. No child output is emitted, durable records acknowledged before the failure stay durable, and interrupted work is never recorded as complete. Cancelling the document cancels and joins every child; no task, -subscription, retained resource or output buffer survives its spawn. +subscription, saved resource or output buffer survives its spawn. **Control flow.** `` is a boundary. A `` inside one cannot select a value owned outside the ``, and a `` inside one cannot exit a @@ -7534,7 +7534,7 @@ not suppress any of these actions. A function component that fails fails the operation it is part of, like any other Effection work. Its invocation is dismantled first — projected content, -then the component's own resources, then anything it retained — so the failure +then the component's own resources, then anything it saved — so the failure that leaves the boundary accounts for the body and its teardown together. An `Error` propagates by identity, keeping its type and `cause`; a thrown non-Error becomes an `Error` carrying the original value as its `cause`. Later siblings do @@ -7985,7 +7985,7 @@ the body before this message can be produced for it. Selection appends no durable event. A partial replay replays completed effects and reconstructs the same ownership while re-executing the authored selection; a -completed document replay reuses the retained terminal result without expanding +completed document replay reuses the saved terminal result without expanding the body. **Selection is the engine's, not the document's.** A document's own eval block @@ -8863,7 +8863,7 @@ may journal what its filesystem provider was asked to do, and one does: the interactive `xmd repl` retains the reads and writes an entry performed in the ambient checkout it was started in, so a partial continuation of that entry answers a completed one from what it produced instead of reading or writing -again (`specs/repl-spec.md`). A retained read answers with the exact text it +again (`specs/repl-spec.md`). A saved read answers with the exact text it read, so the binding its `as` prop produced is the recorded text rather than whatever the file says at continuation time; a read the entry had not reached is new work and reads the file as it is then. @@ -9022,7 +9022,7 @@ same answer. Under a workflow run the deletion is one durable effect. A completed replay restores its recorded outcome and removes nothing a second time, even where the -path exists again; a partial replay continues live from the retained Workspace +path exists again; a partial replay continues live from the saved Workspace frontier and performs the first unrecorded deletion once. Under the interactive `xmd repl` the three mutation replays differ again, and @@ -9030,7 +9030,7 @@ only where the entry is standing in a Git checkout. The host retains what its provider answered, so continuing an interrupted entry answers a completed deletion from its record and removes nothing — including where the path exists again, because nothing is compared with the live filesystem -(`specs/repl-spec.md`). Outside that checkout nothing is retained and the +(`specs/repl-spec.md`). Outside that checkout nothing is saved and the `xmd run` rules above apply as written. The component is the same component in all three: form dispatch, failed-child behavior, containment, the atomic native semantics, the printed and fatal failure split, and generated admission are @@ -9052,7 +9052,7 @@ as a write, under `https://executable.md/components/file/delete`, and that is the identity a continuation is compared against. A write-enabled fragment therefore removes one file by writing this component, and it is this component that runs: an admitted deletion crosses `API.Files` into the run's -transaction-bound provider and is retained by the ordinary `workspace_file` +transaction-bound provider and is saved by the ordinary `workspace_file` effect described above. The evaluator collects nothing from it and invents no receipt, so a fragment that only deletes renders nothing unless it authors output around the deletion. @@ -9416,7 +9416,7 @@ Api; what it does not do is write an `elicit` record. The host's provider suspends the run (§6.17), and a durable wait cannot sit inside another durable operation: the request would be published in the same coroutine ahead of the `elicit` entry that never settles, and the resumed execution would replay that -coroutine and find the wrong entry waiting. So the answer is retained by the +coroutine and find the wrong entry waiting. So the answer is saved by the suspension protocol instead, once rather than twice, and it is claimed at the durable position that published the request. Two questions in one procedure sit at two positions, which is the identity the question fingerprint provides @@ -9645,14 +9645,14 @@ The workflow execution assigns an opaque suspension ID from the run and durable coroutine position. It publishes one ordinary `suspension_request` Yield whose description contains that ID, request and schema. The existing secret gate sees the complete event before persistence. A successful result says the request was -retained; it does not say the wait has an answer. +saved; it does not say the wait has an answer. A stable contextual route lets the operation reach the workflow executor when the component and binary loaded separate copies of the workflow package. The route carries a notice rather than an answer. Middleware may refuse or suppress a descendant's route, but a value it returns cannot make an unanswered `suspendFor()` continue. The executor accepts the notice only when the matching -retained request is at the execution's exact current durable position. +saved request is at the execution's exact current durable position. With no delivered input, the operation remains pending while the trusted host halts the document execution. It raises no document error and returns no value. @@ -9665,7 +9665,7 @@ nothing and append nothing. With no input, the same suspension ID releases the new executor again without duplicating the request. With a schema-validated input, the operation publishes one separate durable -`suspension_answer` Yield before returning that value. Only retained delivery +`suspension_answer` Yield before returning that value. Only saved delivery state can make it do so; the value a contextual handler returns never publishes an answer. Consuming that state and appending the answer are one database transaction, so an interruption cannot consume an answer the journal does not @@ -9682,7 +9682,7 @@ live interaction and its validated answer is the one durable effect. Under `xmd workflow` it is the same mechanism reached from the same element: the host installs an Elicitation provider that calls `suspendFor()` with the rendered message as the request and the compiled schema as the response schema, so the -retained request is what the person was asked and the delivered answer is judged +saved request is what the person was asked and the delivered answer is judged against the schema the document compiled. `xmd workflow answer` recompiles that schema through the same preparation `` used, so one compiler decides both ends. The host chooses no answer and the document chooses no transport. @@ -9690,8 +9690,8 @@ both ends. The host chooses no answer and the document chooses no transport. Any other public Elicitation middleware sits where `` sits: it may answer the question or refuse it, and in both cases the run it affects is a run that never suspended. None of them continues a suspended one — that takes a run -whose status is `suspended`, whose stop reason names the retained request, a -value that satisfies the retained response schema and the secret gate, and a +whose status is `suspended`, whose stop reason names the saved request, a +value that satisfies the saved response schema and the secret gate, and a resume claiming it at the exact position that published it. ### 6.18 Reading over HTTP: `` @@ -9745,7 +9745,7 @@ An explicit `timeout` outranks the contextual `Config.timeoutFetch` default; wit neither, the request carries no bound of its own and the run's own deadline is what stops it. -#### What is retained, and what is bound +#### What is saved, and what is bound The normalized request is what the durable effect carries as its input: @@ -9760,7 +9760,7 @@ The normalized request is what the durable effect carries as its input: It has exactly those fields, and `timeout` only when one was effective. No credential store, provider identity, live header object, response handle, or -host metadata is part of it, and the authored timeout spelling is not retained +host metadata is part of it, and the authored timeout spelling is not saved beside the milliseconds it meant. A settled response is exactly: @@ -9824,7 +9824,7 @@ performs no request, which is substitution rather than reach. other name (§5.3). That shadow is not core's identity: it acquires no permission from the name, and its own requests cross the same host ceiling. -A header a document writes is retained data rather than an authentication +A header a document writes is saved data rather than an authentication mechanism. The component exposes no environment variable, host token, or credential storage, and a credential written into a header crosses the secret gate like everything else. @@ -10022,8 +10022,8 @@ root does not — even under `--verbose`. It opens no scope, acquires no resource, holds no capability and has no durable effect of its own, and contextual verbosity takes no part in durable identity, -authorization, replay admission, retained-effect ownership or journal identity. -Its skipped body is absence, not a retained decision. Replay of a completed root +authorization, replay admission, saved-effect ownership or journal identity. +Its skipped body is absence, not a saved decision. Replay of a completed root is unchanged, and a live or partial expansion reads the value that applies to that execution. @@ -10124,7 +10124,7 @@ leaves no managed root behind: the same number of times, and a later run starts with a new identity, empty evidence and a new request. -The **retained provider** is what a workflow host installs inside its Workspace +The **saved provider** is what a workflow host installs inside its Workspace attachment. Its operations are durable effects keyed by the WorkflowRun, so a completed one restores from the journal without contacting anything. @@ -10163,7 +10163,7 @@ fails — is withheld from the output stream in full; earlier cleared chunks remain observable and become the stream's partial close value. Withholding decides nothing about the run: the journal gate is still what rejects, and the rejected event stays absent. A replayed execution restores -its retained output without rescanning it — the journal it replays crossed the +its saved output without rescanning it — the journal it replays crossed the gate when it was written. Findings and scanner failures carry positions and rule identities, never the @@ -10237,7 +10237,7 @@ and cannot describe and run a document under different identities. ```typescript const fromFile = { path: "hello.md" }; const inline = inlineSource("# Hello"); -const piped = retainedSource("", "# Hello"); +const piped = savedSource("", "# Hello"); ``` Supplied text reports the stable identity ``: `inlineSource` attaches it, @@ -10250,7 +10250,7 @@ durable root import, so the journal holds it and a replay restores it without reading anything. Text a caller piped in reports `` on exactly those terms, through the -retained form: origin and source travel together, the origin is what positions +saved form: origin and source travel together, the origin is what positions and diagnostics carry, and the exact bytes are what the root binding and the durable root import retain. That pair is already the identity of a supplied root, so two different programs piped into two runs are two different roots @@ -10313,7 +10313,7 @@ what it declares — without executing the document or creating a journal: the two apart. - `targets` — every document target the root addresses, as canonical encoded fragments without the document path or a leading `#`, in document order, - duplicates retained (§5.4). + duplicates saved (§5.4). - `targetInfo` — the same targets, in the same order and with the same duplicates, each as a `DocumentTargetInfo` carrying its `target` and, when the section states one, its `description` (§5.4). Informational and additive: @@ -10512,7 +10512,7 @@ same terms it is run on. **The durable protocol is unchanged.** There is no Plugin event, no Plugin-set identity and no generic Plugin record; the root-import and declaration records -are byte-identical. A Plugin that must refuse an incompatible retained history +are byte-identical. A Plugin that must refuse an incompatible saved history supplies a `JournalAdmission`. ### The document expansion @@ -10544,7 +10544,7 @@ cause-free `DocumentProtocolError`. A chain that returns without ever reaching the terminal fails the same way. A refused delegation consumes neither the request nor the expansion, so a handler may recover and delegate a valid one. -A refusal stops later work and is retained as the root's terminal outcome. +A refusal stops later work and is saved as the root's terminal outcome. Already completed preparation effects remain history; a second delegation starts no second expansion. The [document-expansion contract](../architecture.md#capability-backed-document-expansion) defines refusal ordering and complete-terminal admission. @@ -10565,7 +10565,7 @@ mutates and rethrows its error object. Completion reports a fresh `DocumentProtocolError`; [core's private refusal provenance](../architecture.md#capability-backed-document-expansion) never relies on the object's current members. -An identical root source and target may replay a retained pre-import failure +An identical root source and target may replay a saved pre-import failure without preparation, policy, import, authored work or append. A changed root or an unreadable binding refuses. Partial history restores completed preparation effects and continues live. Reuse requires the @@ -10576,7 +10576,7 @@ not a copied binding on an otherwise invalid result. Terminal acceptance stores a **detached** structural copy of the accepted props: objects and arrays are recursively copied and frozen, `"__proto__"` is carried as an ordinary data member rather than assigned, and no container the caller -still holds is retained. A value that is not JSON, and an object that resists +still holds is saved. A value that is not JSON, and an object that resists being read, are refused as a `DocumentProtocolError` built at the boundary rather than rethrown, so neither a hostile trap's message nor the value that produced it reaches the caller. @@ -10615,12 +10615,12 @@ the trusted durable work a host performs inside the durable root. Its property is read exactly once and captured by value before any `install()` runs, and the collection is copied and frozen, so replacing or removing it afterwards changes nothing. Preparations run in installation order, stopping at the first -refusal, **after** retained-history admission and **before** any public +refusal, **after** saved-history admission and **before** any public `Execution.document` policy, the root import and every authored effect. On a completed terminal replay the durable root is already settled and no preparation runs; on a live or partial continuation they run and record through the ordinary durable protocol, so an effect an earlier preparation -completed is restored from its retained record rather than performed again. +completed is restored from its saved record rather than performed again. ### The workflow component bundle @@ -10951,7 +10951,7 @@ and the commands differ: - a workflow execution initializes none at all, because its Workspace attachment owns repositories for the run; - `xmd repl` initializes none either. What an entry does to files in the ambient - checkout it was started in is retained by the entry's own recording, which the + checkout it was started in is saved by the entry's own recording, which the selected REPL host registers rather than this Plugin ([the REPL](./repl-spec.md)). @@ -11252,8 +11252,8 @@ fragment's own scanned positions. | Sample LLM call | `sample` | `sample:{command_preview}` | Only when `sample` modifier is used; Sample Api middleware determines behavior | | Resolve components (glob) | `glob` | `resolve:{dir}` | Only when `useDurableGlobResolver` middleware is installed | | Read over HTTP | `fetch` | `fetch:{expansion id}` | Normalized request in `description.input`; status, detached headers and text body in the result (§6.18) | -| Admit generated XMD | `generated_xmd` | `generated:{fragment id}` | The canonical class selection, retained roots, selected root, every selected entry as a name, identity and admitted forms, and the exact request policy in `description.input`; the admitted source, that same policy, and the identity and form of each element the fragment named in the result (workflow-workspace-spec §8.4) | -| Read the symbols | `syntax_symbols` | `syntax_symbols:{expansion id}` | One per authored `` occurrence. The payload is closed on exactly one member: `{ symbols: string }`, the rendered Markdown the component returned, or `{ refused: non-empty string }`, a named selection canonical core refused. Both are durable values, interpreted only after publication succeeds, so a retained refusal is classified identically live and on replay while a publication failure prevents interpretation and stays terminal. A continuation restores from the record without consulting the filesystem, registry, bundle, host or lexical reference again; existing `{ symbols }` histories remain readable, a missing, additional, mistyped, empty or simultaneous member is stale input and refuses before output or binding, and a cancelled read completes teardown and commits nothing (§5.3.1) | +| Admit generated XMD | `generated_xmd` | `generated:{fragment id}` | The canonical class selection, saved roots, selected root, every selected entry as a name, identity and admitted forms, and the exact request policy in `description.input`; the admitted source, that same policy, and the identity and form of each element the fragment named in the result (workflow-workspace-spec §8.4) | +| Read the symbols | `syntax_symbols` | `syntax_symbols:{expansion id}` | One per authored `` occurrence. The payload is closed on exactly one member: `{ symbols: string }`, the rendered Markdown the component returned, or `{ refused: non-empty string }`, a named selection canonical core refused. Both are durable values, interpreted only after publication succeeds, so a saved refusal is classified identically live and on replay while a publication failure prevents interpretation and stays terminal. A continuation restores from the record without consulting the filesystem, registry, bundle, host or lexical reference again; existing `{ symbols }` histories remain readable, a missing, additional, mistyped, empty or simultaneous member is stale input and refuses before output or binding, and a cancelled read completes teardown and commits nothing (§5.3.1) | ### 10.2 Example journal for a multi-component document @@ -11479,7 +11479,7 @@ visible warning blocks, gather into a separate error report). | EU5 | `null` | A nullable prop receives `null` as an own value, beside an absence in the same document | | EU6 | Failure | An expression that throws is still the positioned expression-prop failure; EP13/EP15 keep the unbound name and EP14 the non-serializable root | | EU7 | Nested | One object member and one array entry take native `JSON.stringify` normalization — dropped and `null` | -| EU8 | Durability and replay | The retained record holds no `undefined` and no record of prop resolution; a partial replay reconstructs the same props through ordinary expansion, and a completed one reuses its terminal result and imports nothing again | +| EU8 | Durability and replay | The saved record holds no `undefined` and no record of prop resolution; a partial replay reconstructs the same props through ordinary expansion, and a completed one reuses its terminal result and imports nothing again | | CP8 | The function-component path | The same rule reaches a function component's props, while the capture beside it is handed the exact `undefined` | ### Tier D — Code execution and modifier middleware @@ -11523,7 +11523,7 @@ visible warning blocks, gather into a separate error report). | FG6 | `silent` with a journal | Displays neither channel; the record keeps both | | FG7 | Non-zero exit | Earlier output survives, the next block never starts, no `` | | FG8 | Cancellation | Child and forwarding stop; nothing arrives afterwards | -| FG9 | An emission during acquisition | The per-exec wrapper is already active; retained exactly, displayed once | +| FG9 | An emission during acquisition | The per-exec wrapper is already active; saved exactly, displayed once | | FG10 | A same-name Context | Cannot suppress a record the host asked for | | FG11 | A same-name Context | Cannot make a transient run retain output | | FG12 | Capture without a journal | Binds its stdout, retains nothing, and the next block forwards again | @@ -11536,13 +11536,13 @@ visible warning blocks, gather into a separate error report). | FG16c | Quiet subprocess output | Hidden from the host, still reported to its caller | | FG17 | A resumed journaled capture | Rebuilds its binding without running the child again | | FG18 | A chunk held up in downstream middleware | Cannot take the other channel's bytes with it | -| FG19 | Enclosing middleware that transforms the bytes | What it forwards is what is displayed and retained | +| FG19 | Enclosing middleware that transforms the bytes | What it forwards is what is displayed and saved | | FG23 | Handlers above and below the boundary | The one above decides the record; the one below decides nothing about it | | FG24 | An upstream redaction | Captured, journaled and replayed as the safe text; the original is nowhere, and replay starts no child | -| FG25 | Upstream consumption | Displayed and retained nowhere; exit status and the other channel are unaffected | -| FG26 | An upstream redirect | Reclassifies the channel the run records; retained stdout is empty | +| FG25 | Upstream consumption | Displayed and saved nowhere; exit status and the other channel are unaffected | +| FG26 | An upstream redirect | Reclassifies the channel the run records; saved stdout is empty | | FG27 | A nonzero `exec as="name"` | Binds status and both channels, displays and renders neither, the run goes on, and the record still keeps the status alone | -| FG28 | A resumed retained bound block | Rebuilds the same binding fields from the record without starting the command again | +| FG28 | A resumed saved bound block | Rebuilds the same binding fields from the record without starting the command again | | FG29 | A command that cannot start | Fails as any block does and binds nothing | | FG30 | A bound block that times out | The timeout still wins as a failure; nothing is bound | | FG31 | A refused annotation or modifier | `silent`, a non-`exec` terminal, an unquoted, duplicated, non-trailing or invalid name — refused, and no process starts | @@ -11751,9 +11751,9 @@ visible warning blocks, gather into a separate error report). | L3 | Persist resource lifetime matches component | Resource is observably gone once the invocation completes, while the document still runs | | L4 | Persistent flag scoped to chain | `persistent` is `true` only during the persist-wrapped chain | | L5 | Multiple persist blocks in one component | Each retains its own resources independently | -| L6 | Persist on repeated run | Resource is created and retained again for the current component lifetime | +| L6 | Persist on repeated run | Resource is created and saved again for the current component lifetime | | L7 | Persist flag does not leak to sibling blocks | Non-persist block after persist block → flag is false | -| L8 | Projected persist stops before the component's own | A projected `persist` resource stops ahead of one the component retained *after* projecting: `start:projected, start:own, stop:projected, stop:own` | +| L8 | Projected persist stops before the component's own | A projected `persist` resource stops ahead of one the component saved *after* projecting: `start:projected, start:own, stop:projected, stop:own` | ### Tier M — Timeout modifier @@ -11778,8 +11778,8 @@ visible warning blocks, gather into a separate error report). | # | Test | Verify | |---|------|--------| | O1 | Eval scope created before durableRun | `resource(useEvalScope())` runs in outer scope, not inside durable execution | -| O2 | Eval scope destroyed on document completion | All retained resources cleaned up when expansion finishes | -| O3 | Invocation scope destroyed with the invocation | A resource retained by a component stops when it completes, while the document keeps running | +| O2 | Eval scope destroyed on document completion | All saved resources cleaned up when expansion finishes | +| O3 | Invocation scope destroyed with the invocation | A resource saved by a component stops when it completes, while the document keeps running | | O4 | Component resource live during projection | A resource a TypeScript component acquires directly is running while its projected content executes | | O5 | Projected content stops first | `start:own, start:projected, stop:projected, stop:own` — no `ephemeral()`, `scoped()` or wrapper in the component | | O6 | Ordering is the boundary's | Same order when the resource is acquired after the first projection: `start:projected, start:own, stop:projected, stop:own` | @@ -12393,7 +12393,7 @@ component that observes one at an authored site. | SYN6 | The origin selection reports | Selection answers `{ kind: "protected", origin: "@executablemd/core" }` — its own kind, not a reserved registration | | SYN11–SYN15 | The import chain | Middleware that answers, substitutes, mutates, redirects, delegates twice or reuses another import's definition cannot run a replacement; ordinary delegation reaches canonical ``; a deliberate middleware refusal stays a refusal; document-authored context and a look-alike reference change nothing | | SYN16–SYN18 | The site described | An ordinary run reports its own includes and registry; a workflow root reports its bundle without importing or running a member; a declared Markdown component's body reports the site it inherited | -| SYN20–SYN22 | The record kept | Continuation restores the retained symbols after the environment moves and rediscovers nothing; missing, additional and wrong-typed payloads refuse before output or binding; a cancelled read completes teardown and commits nothing | +| SYN20–SYN22 | The record kept | Continuation restores the saved symbols after the environment moves and rediscovers nothing; missing, additional and wrong-typed payloads refuse before output or binding; a cancelled read completes teardown and commits nothing | | SYN23, SYN25b | Never permission | Symbols naming a component neither register, resolve nor authorize it; a fixed narrower reference answers with exactly the symbols it was handed and adds nothing — the seam `` installs through | | SYN24 | One description | Inspection and validation describe the component identically, from one declaration | | SYN26 | Another loaded copy | A protected implementation built by a second loaded copy answers for nothing in the active execution | @@ -12401,7 +12401,7 @@ component that observes one at an authored site. | SYN28 | Pinned provenance | A workflow-bundle component is reported at its path *and* blob object id, under the `workflow` origin kind, and stays in the user-provided category | | SYN29 | The named form | Selected entries render their metadata and documentation once each in symbol order, not request order, with nothing else of the symbols; `as` binds identical text | | SYN30 | Availability and absence | Each entry states whether it is available in the current evaluation; a selected entry with no authored documentation renders its metadata and says so | -| SYN31 | Atomic refusal | An unknown name, an empty list, a duplicate, a non-string member, a non-array value and an undeclared prop each refuse with no successful retained result | +| SYN31 | Atomic refusal | An unknown name, an empty list, a duplicate, a non-string member, a non-array value and an undeclared prop each refuse with no successful saved result | | SYN39 | Named retention | The occurrence retains its final rendered text, a continuation restores it without rereading documentation or rebuilding the symbols, and a corrupted record refuses | | SYN25c | The narrowing seam | A narrowed reference reports the narrowed vocabulary bare, documents the enclosing symbols by name, and marks each entry's availability truthfully in both directions | | SYN32–SYN34 | Parsing one file | Bundle prose, a section per level-two heading with deeper headings kept inside it, a fenced heading read as the example it is, and a refusal for a duplicate section or a heading that is not a component name | @@ -12445,13 +12445,13 @@ through the captured capability because there is no other way to reach it. | FE11 | Completed Plan work and completed fragment effects replay without repetition. | | FE12 | Hostile or malformed durable records fail closed before effects. | | FE13 | Declared-component private closures and producer-private capabilities remain unavailable. | -| FE14 | Middleware answer A is retained by the publication its provider made on that exact object; continuation with B, offered under another identity by its own declaration, refuses before A or B runs; unchanged A resumes. | +| FE14 | Middleware answer A is saved by the publication its provider made on that exact object; continuation with B, offered under another identity by its own declaration, refuses before A or B runs; unchanged A resumes. | | FE15 | Provider registrations disappear at teardown; a losing or cancelled resolution's publication cannot later execute. | | FE16 | Repeated Evaluate occurrences cannot consume one another's records. | | FE17 | Actual cancellation stops active work, waits for cleanup, and records no false success. | | FE18 | One closed current record shape, marked as itself; a record an earlier build wrote is refused as unreadable history before the admission it would have restored performs anything. | | FE19 | Canonical `text` produces identical output across fresh workflow executions without warnings; `source` and `program` refuse before fragment effects under every profile. | -| FE20 | Exact trusted Fetch limits are retained and narrowed; missing or changed limits refuse before requests. | +| FE20 | Exact trusted Fetch limits are saved and narrowed; missing or changed limits refuse before requests. | | FE21 | Ordinary and workflow hosts exercise the same fragment evaluator rather than separate semantic implementations. | | FE22 | `xmd plan` output remains ordinary text usable at the command boundary and acquires no special complete-root type. | | FE23 | An evaluation lacking write permission refuses the write before effects, including when the write component appears in trusted symbols. **Receives the Evaluate clause of #758's SY21**; the half that stays in #759 is that symbols text alone registers, resolves and authorizes nothing. | @@ -12464,7 +12464,7 @@ through the captured capability because there is no other way to reach it. | FE30 | A trusted nested-run or evaluation-host layering control enters the same declarative package bootstrap through inherited and local layers. The child keeps the package registration and renders the same named documentation as the single-bootstrap control; Evaluate still admits only the identity selected by `allow`. A non-identical owner/component overlap refuses before child root or fragment effects. This receives #765's SYN25l.5 rather than creating a second collector in Evaluate. | | FE31 | One fragment binds File and protected Syntax results locally and explicitly renders a chosen object through Json. Nested literal arrays and objects, binding shorthand, finite JSON numbers and a directly attached leading minus such as `` work in scalar, array and object positions. `1e999`, `-1e999`, `+1`, `-note`, `!note`, `typeof note`, an unbound identifier, call, operator, spread, computed property, template or global reference is refused before any earlier effect. | | FE32 | Generated admission and effect events follow ordinary persist-before-resume publication. A later refusal, failure, cancellation or interruption retains completed effects, and continuation resumes at the occurrence's first unrecorded effect; Evaluate adds no staging or rollback. | -| FE33 | Each Evaluate occurrence retains its admission and generated effects under identities belonging to that occurrence; two occurrences cannot consume one another's retained work, and no caller-controlled context, stream, replay cursor, coroutine identifier or durable owner participates. A projected body installs observable structured cleanup, and later parent work proves that cleanup completed before it began. No distinct child cursor, child `Close`, staging, rollback, provisional publication or second settlement protocol is required. | +| FE33 | Each Evaluate occurrence retains its admission and generated effects under identities belonging to that occurrence; two occurrences cannot consume one another's saved work, and no caller-controlled context, stream, replay cursor, coroutine identifier or durable owner participates. A projected body installs observable structured cleanup, and later parent work proves that cleanup completed before it began. No distinct child cursor, child `Close`, staging, rollback, provisional publication or second settlement protocol is required. | | FE35 | A fragment composes admitted reads with ``, ``, ``/`` and ``/`` under ordinary rules, performing exactly the reads the taken paths name. A prohibited component in an untaken `` arm or an unreached `` refuses the whole fragment with zero reads, and the refusal names the selected permission rather than the branch. An `` item binding does not escape its body while `` does, and a binding the body itself makes cannot be read after the construct. Neither arm of an `` nor either `` of a `` can read a binding another alternative produces; each such refusal performs no read. A `` holding a valid `` and a `` region around an admitted read completes, proving those constructs reach their own handlers rather than a generic structural refusal. A prohibited component inside a paired ``'s template children, and one in the ordinary `` body, are each refused before any effect. An untaken self-closing `` before a selected paired `` write does not surrender its admission to that write: the write performs, the read does not, and the untaken file is unchanged. A two-item `` around one `` performs the read twice with no import refusal, an empty iteration consumes no admission, and a continuation cut between the two repeated effects restores the completed one and resumes at the second. An executable fence and an interpolated caller binding stay refused inside a construct. An ill-formed construct, one the generated root gives no context for, a stray branch and a `` outside every `` are refused as structural mistakes rather than as withheld components. | | FE34 | One fragment binds Glob, File and canonical Syntax and renders chosen findings through Json; an unmatched search binds `[]`. A search written without `as`, a paired search, and a paired `` under a `read` selection each refuse before any operation. An unusable pattern refuses with the ordinary sentence before the search, and a provider failure becomes one safe sentence naming no path. The search reaches the installed `API.Files` provider and the contextual working directory, so one middleware around the document sees the fragment's request and a transformation it makes is what the provider performs. Bare generated Syntax reports the composition table plus the selected read vocabulary and not the unselected writing spellings; named Syntax describes a component that stays unavailable. Canonical Syntax resolves at core's own identity, and a middleware replacement of that answer refuses. Selecting `` refuses at capture, because its declaration offers no generated form. `xmd run` and a `host="run"` child of `xmd test` offer the same selection. | @@ -12537,8 +12537,8 @@ every refusal is proven by the phase tripwires that stayed at zero. | # | Test | Verify | |---|------|--------| -| PS1–PS3 | Fixed grammar | One request preserved byte for byte and a second positional refused; every retained option accepted before and after it; every `--run` spelling answered with the migration, and every other removed option — both short aliases and the aggregate and generated property names included — answered with the one refusal that names `xmd run`, before any symbols, Agent, session, review or filesystem activity, and before `--help` can short-circuit the dispatch in either order; a name that merely begins like a property option keeps the generic unknown-option refusal; a first token of `prompt` refused in preflight rather than read as a document path, with `xmd run ./prompt` still executing a document of that name | -| PS4/PS5 | Help | The complete `xmd plan --help` output and the program summary carry only the retained grammar, both explicit compositions and the journal warning, and no removed option appears in either; `xmd run --help` still exposes every option it configures | +| PS1–PS3 | Fixed grammar | One request preserved byte for byte and a second positional refused; every saved option accepted before and after it; every `--run` spelling answered with the migration, and every other removed option — both short aliases and the aggregate and generated property names included — answered with the one refusal that names `xmd run`, before any symbols, Agent, session, review or filesystem activity, and before `--help` can short-circuit the dispatch in either order; a name that merely begins like a property option keeps the generic unknown-option refusal; a first token of `prompt` refused in preflight rather than read as a document path, with `xmd run ./prompt` still executing a document of that name | +| PS4/PS5 | Help | The complete `xmd plan --help` output and the program summary carry only the saved grammar, both explicit compositions and the journal warning, and no removed option appears in either; `xmd run --help` still exposes every option it configures | | C2–C3 | The packaged adapter and Component | The command executes the checked-in Markdown value root under ``, which invokes the packaged `` Component, and the turn text is that Component's own words; generation, repair, review, revision, approval, stopping, exhaustion and the final explanation are Markdown under visible headings, every Plan-producing turn states the complete Plan requirements for itself, `` stays one turn, and what a person reads says each thing once however many rounds it took | | C4–C6 | Session and ceiling | One enclosing Session carries every turn, defaults differ per invocation and `--session` supplies the exact override; the Plan writer profile gives the assistant an empty host-owned directory, no MCP servers, no native tools and a private strict denial no command line reaches; a draft is data throughout, and no draft effect ever happens | | C7–C9 | Classification, bounds and presentation | Draft defects return structured facts, and a root declaring required properties is not one of them; one base draft plus three repairs, and ten presentations with no revision on the last; arbitrary source cannot close ``, the review schemas expose exactly the friendly choices for each round and state, and stopping, exhaustion and the explanation ending each reach their own authored `` | @@ -12560,7 +12560,7 @@ rather than restating. |---|------|--------| | PO1–PO3 | Phases and counters | Each phase precedes the work it names, and one reaches the operator while a turn is still blocked; the repair and attempt ordinals come from the loop bounds and stop at them; Stop and exhaustion announce themselves and keep their exact endings | | PO4/PO5 | Surface and disclosure | Neither ordinary `` form announces anything or expands a progress body; default progress holds no request, draft, diagnostic, feedback or approved source, and verbose adds exactly the two blocks, in phase order | -| PO6/PO7 | Channels and grammar | A non-terminal stderr receives normalized Markdown and a stated terminal receives it rendered, while stdout and `--output` stay byte-identical; `--verbose` and `--journal` work on either side of the request, help carries them and the journal warning, and the short aliases, every removed spelling and a retained option that reaches this grammar written where the journal path goes all refuse before any work, while `--help` keeps its ordinary precedence | +| PO6/PO7 | Channels and grammar | A non-terminal stderr receives normalized Markdown and a stated terminal receives it rendered, while stdout and `--output` stay byte-identical; `--verbose` and `--journal` work on either side of the request, help carries them and the journal warning, and the short aliases, every removed spelling and a saved option that reaches this grammar written where the journal path goes all refuse before any work, while `--help` keeps its ordinary precedence | | PO8/PO9/PO16 | The journal file | No `--journal` writes no file; one creates the path before the symbols and the first turn, parses as the existing JSONL in commit order, ends terminally and holds no program execution; an existing path and an uncreatable one each report their exact refusal and reach nothing; and an ordinary failure — where no append failed — leaves a wholly parseable file with no partial trailing record | | PO10–PO12 | The secret and persistence boundaries | A secret in a draft or in a failed check's findings reaches neither the progress nor the file while the earlier prefix stays readable, and the same values without it are shown and recorded; a refused entry reports the exact journal-write diagnostic and preserves what committed | | PO13–PO15 | Failure and ordering | A progress destination that fails cancels the live turn, waits for every owned teardown and delivers nothing; every existing ending keeps its order and no phase claims delivery; the packaged adapter says nothing of its own and the symbols are observed once, through public ``, while continuation restores that observation without rebuilding it | @@ -12597,7 +12597,7 @@ Provider-neutral, and portable across every runtime. Defined in §5.3 and §8.1. | WB15 | Ordinary execution | `execute()` installs no catalog: a handler still answers an import | | WB16/WB17/WB18 | Journal shape | One selection per import holding exactly `kind`, `path`, `sourceHash` and `content`; an exact replay reconstructs from the record and resolves nothing; an unreadable record is one fixed diagnostic | -### Tier WBA — Holding retained history to the bundle +### Tier WBA — Holding saved history to the bundle Provider-neutral, and portable across every runtime. Defined in §5.3. @@ -12692,11 +12692,11 @@ Provider-neutral, and portable across every runtime. Defined in | WD25/WD26 | Bundle round trip | A descriptor closed over no bundle writes no `components` member; a bundled one round-trips a sorted five-entry array unchanged | | WD27 | Bundle hashes | A source hash is held to the length and case the descriptor's own object format requires | | WD28/WD29 | Canonical bundle | Sorted by name, one entry per name, non-empty; each entry closed and naming a repository-relative POSIX Markdown path | -| WD30 | Retained descriptors still read | A descriptor stored before the member existed parses, means "closed over no components", and serializes back byte for byte; a written member naming no bundle is refused | +| WD30 | Saved descriptors still read | A descriptor stored before the member existed parses, means "closed over no components", and serializes back byte for byte; a written member naming no bundle is refused | | WD31 | No echo | A refusal never quotes a component's name, path or hash | | WD32/WD33/WD34/WD35 | Bundle identity | The same bundle is the same run; a changed name, path, hash or set conflicts as `definition`; a run closed over a bundle is never one closed over none; what a run accumulates still takes no part | -### Tier WS — Retained workflow runs +### Tier WS — Saved workflow runs Runs against the production Deno adapter, on real files. Defined in [Workflow runs](./workflow-spec.md) §9. @@ -12721,9 +12721,9 @@ Runs against the production Deno adapter, on real files. Defined in | WS23–WS26 | Values a record cannot use | A descriptor that describes nothing, an empty identity or base, a timestamp that is not an instant or names a day that never happened, a stored stop time, an empty journal identity, and props that are not an object are refused without quoting what they held | | WS24d | A number too large to hold | A 64-bit stored value reaches a parser instead of escaping as an error quoting it | | WS27 | Damage | A scribbled page is reported as damage, and the file stays where it is | -| WS33a–WS33c | Version 1 amended for answers | Version 1 declares the retained answer table and creates it empty; the shape before it is refused unchanged as an incomplete pre-release; an answer table rebuilt without its constraints is damage rather than a row to read | +| WS33a–WS33c | Version 1 amended for answers | Version 1 declares the saved answer table and creates it empty; the shape before it is refused unchanged as an incomplete pre-release; an answer table rebuilt without its constraints is damage rather than a row to read | -### Tier WJ — The retained journal and caller-owned transactions +### Tier WJ — The saved journal and caller-owned transactions Defined in [Workflow runs](./workflow-spec.md) §9.5–§9.6. @@ -12776,13 +12776,13 @@ Defined in [Workflow runs](./workflow-spec.md) §9.5–§9.6. | DLC12 | Workspace replay | Replayed Workspace operations require no live provider | | DLC13 | Runtime-neutral boundary | No module of the shared coordination surface names a storage, connection, savepoint or transaction-token type outside its own prose, reads a host global, or loads a module only one host can resolve. Host globals (`process`, `Deno`, `Bun`, `Buffer`, `globalThis`, `navigator`, `__dirname`, `__filename`) are recognized as parsed identifier references, so a word containing one and a property of that name are not crossings, while cross-runtime Web APIs such as `crypto` are never crossings. Module loading is read from parsed syntax — static imports and re-exports, `import type`, type-position `import()`, dynamic `import()`, `import =` and `require()` — with quoted and no-substitution template specifiers decoded, so comments and strings that merely contain import syntax load nothing. A destination the surface computes cannot be shown not to be a host module and is refused. Host schemes (`node:`, `bun:`, `deno:`, `cloudflare:`, `workerd:`), whole path segments naming any runtime this repository builds an entry point for (`deno`, `node`, `bun`, `compiled`, `cloudflare`, `workerd`), vendored sources and host process modules are classified by shape rather than by an enumerated list | | DLC14 | Provider infrastructure failure | A selected coordinator activates one first failure by identity and fences later execution and publication | -| DLC15 | One-shot Workspace invocation | A provider can use the execution-owned invocation capability only during its original call; retained execution, publication and failure operations are refused after completion | -| DLC16 | Loaded-copy Workspace selection | A provider installed by one physical package copy coordinates one operation created by another copy exactly once without sharing a capability through context or a module registry; substituted selection and retained handles remain fail-closed | +| DLC15 | One-shot Workspace invocation | A provider can use the execution-owned invocation capability only during its original call; saved execution, publication and failure operations are refused after completion | +| DLC16 | Loaded-copy Workspace selection | A provider installed by one physical package copy coordinates one operation created by another copy exactly once without sharing a capability through context or a module registry; substituted selection and saved handles remain fail-closed | | DLC17 | Forged contextual completion | A same-named invocation middleware that returns a forged successful response reaches no provider, executor or publication; fail-stop prevents Yield and Close persistence and fences later durable work | | DLC18 | Missing-provider phase refusal | No same-named invocation middleware or execution capability is reached when provider selection is missing | | DLC19 | Authoritative published result | Middleware that delegates and replaces the returned response cannot replace the exact Result recorded by the execution-owned publication | | DLC20 | Post-completion isolation | Throwing, suppressing or delegating twice after authoritative completion cannot alter the result or repeat provider, execution or publication work | -| DLC21 | Retained continuation refusal | A contextual continuation retained beyond its original live invocation is stale and cannot repeat execution or publication | +| DLC21 | Saved continuation refusal | A contextual continuation saved beyond its original live invocation is stale and cannot repeat execution or publication | | DLC22 | Minimum-priority invocation isolation | An enclosing same-named minimum-priority handler receives no invocation capability or operational phase and cannot acknowledge publication without delegation | | DLC23 | Minimum-priority failure isolation | A minimum-priority collision cannot replace first-failure activation; the exact execution-owned failure remains authoritative and no event persists | @@ -12801,10 +12801,10 @@ workspaces](./workflow-workspace-spec.md) §13. | WAC6 | Cancellation | Cancellation before mutation, during mutation and during child teardown publishes no state or event | | WAC7 | Infrastructure fail-stop | A caught provider infrastructure failure retains identity, rolls back everything and prevents later Workspace and ordinary durable execution | | WAC8 | Concurrency isolation | A second same-run handle waits cooperatively without enlisting while a different run remains usable inside the outer transaction scope | -| WAC9 | Replay | A retained Workspace Yield bypasses the Deno coordinator and mutation completely | +| WAC9 | Replay | A saved Workspace Yield bypasses the Deno coordinator and mutation completely | | WAC10 | Effect admission | Exact proof executors work; missing, symbol-forged, foreign, closed and stale handles are refused before savepoint SQL or mutation | | WAC11 | Journal provenance | The selected raw journal and explicitly preserved trusted wrappers of it, nested, publish and commit; an in-memory stream, another run's journal, copied properties, the former symbol name, a custom look-alike, an ordinary guard and a wrapper another loaded copy tried to prove are refused before savepoint, mutation or publication | -| WAC12 | Post-publication rollback | A transaction-owner failure after the routed append rolls mutation, retained root, pointer and event back and fences later work | +| WAC12 | Post-publication rollback | A transaction-owner failure after the routed append rolls mutation, saved root, pointer and event back and fences later work | | WAC13 | Pre-commit cancellation | Cancellation after routed publication but before commit retains no mutation, root, pointer, Yield or Close | | WAC14 | DOFS continuation lifetime | The proof adapter uses only pinned synchronous byte operations; cancellation before a call performs no mutation, cancellation after it rolls back, mutation teardown precedes savepoint rollback, and the connection remains usable | | WAC15 | Mutation teardown failure | Child-teardown failure rolls the mutation and outer transaction back, activates that infrastructure failure and fences later work | @@ -12813,15 +12813,15 @@ workspaces](./workflow-workspace-spec.md) §13. | WAC18 | Savepoint SQL failure | Actual savepoint create, rollback and release refusals poison the coordinated outer transaction and publish nothing | | WAC19 | Middleware publication isolation | Enclosing default-order Workspace middleware receives only provider selection and cannot replace publication with a no-op; mutation, root, pointer and one filtered Yield still commit together | | WAC20 | Middleware failure isolation | Enclosing Workspace middleware cannot replace failure activation; provider infrastructure failure rolls back all state, retains exact identity and fences later effects | -| WAC21 | Selection refusal | A substituted or foreign provider selection is rejected before transaction or savepoint work and leaves no mutation, retained root, pointer change, Yield or Close | -| WAC22 | Minimum-priority publication isolation | A minimum-priority same-named handler cannot observe or acknowledge publication; the real mutation, retained root, current pointer and filtered Yield commit together | +| WAC21 | Selection refusal | A substituted or foreign provider selection is rejected before transaction or savepoint work and leaves no mutation, saved root, pointer change, Yield or Close | +| WAC22 | Minimum-priority publication isolation | A minimum-priority same-named handler cannot observe or acknowledge publication; the real mutation, saved root, current pointer and filtered Yield commit together | | WAC23 | Minimum-priority failure isolation | A minimum-priority same-named handler cannot replace infrastructure-failure activation; the exact first failure rolls back mutation, roots and journal and fences later work | -| WAC24 | Real host crash | A `SIGKILL` while the mutation, immutable root, current-root pointer and routed journal row are written and uncommitted leaves a second connection seeing only the baseline, and a fresh process recovers the baseline filesystem, root, retained counts and journal exactly | +| WAC24 | Real host crash | A `SIGKILL` while the mutation, immutable root, current-root pointer and routed journal row are written and uncommitted leaves a second connection seeing only the baseline, and a fresh process recovers the baseline filesystem, root, saved counts and journal exactly | | WAC25 | Committed restart | A second process reopens a run whose Workspace effects committed in a process that has ended, observes the same filesystem, current root, ordered events, event identities and event-to-root associations, and performs no recorded effect again | -| WAC26 | Historical reconstruction | A fresh process selects an older event's root through the adapter-private materializer, invalidates the authoritative negative resolution, rebuilds its exact topology, bytes, modes, hardlinks and symbolic links from that root's retained DOFS content, and resnapshots to the selected identity | +| WAC26 | Historical reconstruction | A fresh process selects an older event's root through the adapter-private materializer, invalidates the authoritative negative resolution, rebuilds its exact topology, bytes, modes, hardlinks and symbolic links from that root's saved DOFS content, and resnapshots to the selected identity | | WAC27 | Storage-view commit | A row written through the mutation's storage view and its filesystem bytes commit together against the root its journal event names ([workspace-effect-transaction.test.ts](../packages/workflow/tests/workspace-effect-transaction.test.ts)) | | WAC28 | Storage-view rollback | A refused mutation rolls back its storage-view row and filesystem bytes, retains no new root and records its failed Yield against the baseline root ([workspace-effect-transaction.test.ts](../packages/workflow/tests/workspace-effect-transaction.test.ts)) | -| WAC29 | Mutation capability lifetime | Storage, filesystem and savepoint capabilities retained by a completed mutation refuse even while its enclosing transaction remains open before commit ([workspace-effect-transaction.test.ts](../packages/workflow/tests/workspace-effect-transaction.test.ts)) | +| WAC29 | Mutation capability lifetime | Storage, filesystem and savepoint capabilities saved by a completed mutation refuse even while its enclosing transaction remains open before commit ([workspace-effect-transaction.test.ts](../packages/workflow/tests/workspace-effect-transaction.test.ts)) | ### Tier WTX — WorkflowRun savepoints and transaction ownership @@ -12840,14 +12840,14 @@ Defined in [Workflow runs](./workflow-spec.md) §9.6. | WTX9 | Lease and generation fences | Closed leases and stale connection generations cannot recover private handles | | WTX10 | Savepoint rollback cache coherence | A failed mutation restores the file and both authoritative caches before the caller continues and commits the outer transaction | -### Tier WRR — Immutable retained Workspace roots +### Tier WRR — Immutable saved Workspace roots Defined in [Workflow runs](./workflow-spec.md) §9.4 and §9.6–§9.7. | # | Test | Verify | |---|------|--------| | WRR1 | Canonical empty root | Fresh complete-v1 storage retains the exact root-only canonical manifest and its content-addressed identity | -| WRR2 | Complete canonical topology | Paths, metadata, symlinks and deterministic hardlinks form canonical bytes; DOFS manifests and blobs are retained exactly without copied file bytes | +| WRR2 | Complete canonical topology | Paths, metadata, symlinks and deterministic hardlinks form canonical bytes; DOFS manifests and blobs are saved exactly without copied file bytes | | WRR3 | Mutation-derived roots | Create, overwrite, delete, rename, directory, mode, symlink and hardlink changes produce the corresponding immutable roots | | WRR4 | Historical restoration | An older root restores exact topology and content, resnapshots to its identity and clears authoritative negative caches | | WRR5 | Restoration rollback | A restoration failure rolls back its savepoint and preserves the prior live frontier and current-root pointer | @@ -12856,7 +12856,7 @@ Defined in [Workflow runs](./workflow-spec.md) §9.4 and §9.6–§9.7. | WRR8 | Teardown before validation | Child cleanup finishes before final live/current validation, and a stale capture cannot commit | | WRR9 | No unsafe garbage collection | The production closure neither exposes nor invokes Cloudflare DOFS garbage collection | | WRR10/WRR10b | Outer rollback cache coherence | Failure and cancellation after an uncommitted removal and negative lookup roll back and invalidate both authoritative DOFS caches | -| WRR11 | Historical file size | Every historical file entry's declared size agrees with its retained DOFS manifest during read-only recognition | +| WRR11 | Historical file size | Every historical file entry's declared size agrees with its saved DOFS manifest during read-only recognition | ### Tier DT — Document target symbols, selectors, and projection @@ -12881,8 +12881,8 @@ Defined in [Workflow runs](./workflow-spec.md) §9.4 and §9.6–§9.7. | DT33/DT48 | Canonical exactness | A level is canonical only through decode, normalize and re-encode — NFD, tabs, uncollapsed or edge whitespace, lowercase escapes, empty levels and raw operators are refused | | DT49 | Raw `#` | A raw `#` is never a literal selector character; `%23` addresses a heading containing one | | DT50/DT51 | Formatter totality | A path that cannot encode losslessly — NUL, an unpaired surrogate — is refused, and every formatted reference parses back to what it named | -| DT34–DT39 | Projection | Preamble, ancestor direct content and the selected subtree are retained; siblings are absent; a non-leaf keeps its descendants; a sole title stays | -| DT40–DT43 | Positions | A retained element keeps its authored offset and line, CRLF included; frontmatter, props and return mode survive; the untargeted parse still scans the whole body | +| DT34–DT39 | Projection | Preamble, ancestor direct content and the selected subtree are saved; siblings are absent; a non-leaf keeps its descendants; a sole title stays | +| DT40–DT43 | Positions | A saved element keeps its authored offset and line, CRLF included; frontmatter, props and return mode survive; the untargeted parse still scans the whole body | | DT44–DT47 | Inspection | The symbols report without selecting; a glob resolves to the exact target; an unresolvable target fails inspection; the failure's data is frozen and rebuilt | | DT67–DT78 | Descriptions | The first direct static paragraph describes its section, past blank lines and comment-only HTML, kept whole with formatting reduced and whitespace collapsed; a fence, component, list, quote or child heading first leaves no description and later prose is not reached; an interpolation or an inline component omits the paragraph whole rather than as a static prefix | | DT79/DT80 | Structured symbols | `targetInfo` carries the same targets, order and duplicates as `targets`, each with its own description, on the unselected path and beside an unchanged exact `target` on the selected one | @@ -12899,10 +12899,10 @@ Defined in [Workflow runs](./workflow-spec.md) §9.4 and §9.6–§9.7. | # | Test | Verify | |---|------|--------| | TX1–TX3 | Selection | Only the preamble, ancestors and subtree expand; a skipped sibling's components and code blocks never run; a non-leaf expands its descendants | -| TX4/TX5 | Identity | A retained element keeps the expansion ID it has in a full run, and two targets retaining it agree | +| TX4/TX5 | Identity | A saved element keeps the expansion ID it has in a full run, and two targets retaining it agree | | TX6 | Sources | A file root and an inline root behave identically | | TX7–TX9 | Root values | Root props, frontmatter interpolation, a declared `returns`, and `` apply to the projected body | -| TX10/TX11 | Structure | An invalid skipped sibling is irrelevant; an invalid retained range fails before any authored effect | +| TX10/TX11 | Structure | An invalid skipped sibling is irrelevant; an invalid saved range fails before any authored effect | | TX12–TX14 | Failure timing | An unmatched or ambiguous target runs no authored effect and is structurally recognizable; inspection resolves without expanding a component | | TX15/TX16 | Recording | The journal records the exact target, never the glob, and an untargeted run records no target member | | TX17 | Compatibility | A different selector naming the same section replays | @@ -12913,27 +12913,27 @@ Defined in [Workflow runs](./workflow-spec.md) §9.4 and §9.6–§9.7. | TX28–TX32 | Malformed records | Starting from a valid failed-selection journal and corrupting only the record: a missing or non-array symbols, an unknown kind, extra record or failure data, symbols or target the recorded document does not derive, and inconsistent kind/matches data are each refused before completed-Close reuse, resumed with the failing selector and with a valid one, expanding nothing and appending nothing | | TX33 | Not vacuous | An uncorrupted record still replays its recorded failure | | TX34–TX37 | Totality, inside the value | Recorded markdown whose frontmatter no parser accepts, an unreadable member, a record refusing key enumeration, and a value that is not a record at all each become the one fixed diagnostic — cause-free, carrying no planted value, expanding nothing and appending nothing | -| DT63–DT66 | Preamble boundary | A section before the title is addressable and is not preamble; selecting a later section neither renders nor executes it; it stays independently addressable; real preamble text before the first heading is still retained | +| DT63–DT66 | Preamble boundary | A section before the title is addressable and is not preamble; selecting a later section neither renders nor executes it; it stays independently addressable; real preamble text before the first heading is still saved | | TX44/TX45 | Preamble execution | Selecting the later section runs no component of the earlier one, which still runs when it is the target | | TX46 | Nested substitution | A nested `target` accessor answering Alpha then Beta cannot substitute a section: Alpha alone executes, the member is read once, and the appended Close describes Alpha | | TX47–TX50 | Terminal history | Targeted and untargeted completed journals with the root import removed, and one with it duplicated, are refused before terminal reuse; an intact journal still replays | | TX51–TX56 | Identity ownership | A completed or partial Alpha journal resumed as Beta is refused with an enclosing `check` handler that never delegates, with the equivalent `admit` handler, and with a same-name guard from another loaded copy; same-target replay and ordinary guard composition are the controls | | TX57–TX60 | Terminal binding | A root import on a child coroutine, a valid one plus a root-named child event, none at all, and two on the terminal coroutine each refuse before terminal reuse | | TX61 | Detached but mutable | A partial replay restores a binding from the journal and the live continuation writes to it, matching the complete run exactly | -| TX68 | Shifting terminal coroutine | A Close that moves from a child to the root returns nothing: the fixed diagnostic, no retained output, nothing expanded, nothing appended, the coroutine asked once | +| TX68 | Shifting terminal coroutine | A Close that moves from a child to the root returns nothing: the fixed diagnostic, no saved output, nothing expanded, nothing appended, the coroutine asked once | | TX69–TX71 | Post-admission replacement | Public guard policy rewriting the backend's own terminal result does not change what replay returns; a terminal result that refuses is the fixed diagnostic, read once, with nothing expanded and nothing appended; the intact control replays | | TX64–TX67 | Validation order | An unresolvable target outranks an invalid schema on inspection, live execution, and replay; a resolvable target lets the schema failure be reported; the control runs | | TX43 | One read across phases | Two valid recorded selections behind one accessor — Alpha then Beta — resume as Alpha: Alpha's section executes, Beta's never does, the source is read once, and the appended Close describes the Alpha execution | | TX38–TX41 | Totality, on the envelope | A result that refuses to be read, a value that refuses to be read, a settlement that refuses to be read, and a successful result with no value are each malformed rather than unrelated — the fixed cause-free diagnostic, no recorded terminal result reused, no planted text anywhere, nothing expanded and nothing appended, for the original failing selector and for a different selector that would otherwise succeed | | TX42 | Ordinary failed settlement | A root import recorded as failed for non-selection reasons is left alone by this protocol | -### Tier RR — Retained workflow-run installation +### Tier RR — Saved workflow-run installation Defined in [Workflow runs](./workflow-spec.md) §3.1. | # | Test | Verify | |---|------|--------| -| RR1 | Exact record | The retained run is recorded verbatim; no identifier is allocated and Git is never consulted | -| RR2/RR3 | Restoration | A truncated and a completed journal each restore the retained run without recording it again | +| RR1 | Exact record | The saved run is recorded verbatim; no identifier is allocated and Git is never consulted | +| RR2/RR3 | Restoration | A truncated and a completed journal each restore the saved run without recording it again | | RR4 | A different run id | Refused as `StaleInputError` naming the field, never either value, and the root document does not expand | | RR5 | A different bundle or target | Refused on the same terms as RR4, naming `bundleHash` or `targetPath` rather than either value | | RR6 | A malformed record | Refused rather than coerced, without quoting what the journal held | @@ -12948,7 +12948,7 @@ Defined in [Workflow runs](./workflow-spec.md) §3.1. | RR15 | Failed establishment in a truncated history | A recorded `workflow_run` failure fails closed rather than replaying its planted text for a run storage already describes | | RR16 | What a refusal retains | Inspecting the whole error object — not only its message — finds no immutable run-identity values or planted description members; historical `base` and `pinnedCommit` input is not reintroduced as run identity | | RR17 | Suppressed or reordered `Execution` policy | A handler that hands the durable run a different stream — the package's own descriptor or one built elsewhere, registered before or after the workflow installation — cannot make another run's journal replay | -| RR18 | One retained snapshot | A journal whose recorded value shifts between reads is settled once: identity admission, guard observation and replay are handed the same objects, and the second answer is never reached | +| RR18 | One saved snapshot | A journal whose recorded value shifts between reads is settled once: identity admission, guard observation and replay are handed the same objects, and the second answer is never reached | | RR20 | A hostile recorded value | A value whose `ownKeys`, `getOwnPropertyDescriptor` or getter refuses, and one whose classification refuses, each become the same fixed refusal — nothing reused, executed, emitted or appended, and the whole error object carries none of the planted text | | RR21 | A duplicate canonical record | Two entries under the canonical run identity are refused in every settlement combination — successful+successful, successful+failed, failed+successful, failed+failed — because the record is written once and a second entry describes a second run however it ended | @@ -12982,8 +12982,8 @@ Defined in [Workflow runs](./workflow-spec.md) §10. | WF22 | Cancelled deletion | A halt between the removal and the commit publishes no outcome and moves no root; the continuation, which has no record of it, performs and records the deletion exactly once | | WF23 | Atomic directory ensure | A recursive creation, its one `workspace_file` outcome and the resulting current Workspace root become visible together; content begins only after that commit | | WF24 | Directory refusal rollback | A target or intermediate non-directory rolls its savepoint back, publishes only the sanitized refusal against the unchanged root and begins no content; neither a partial parent nor host detail survives | -| WF25 | Existing directory and retained root | Ensuring an existing populated directory preserves its contents and commits a successful effect whose resulting root is the run's authoritative current root | -| WF26 | Directory replay and cancellation | Completed replay restores the recorded success and retained root without creating again; cancellation before commit publishes no effect or root, while cancellation of later content leaves the committed directory and restores the enclosing cwd | +| WF25 | Existing directory and saved root | Ensuring an existing populated directory preserves its contents and commits a successful effect whose resulting root is the run's authoritative current root | +| WF26 | Directory replay and cancellation | Completed replay restores the recorded success and saved root without creating again; cancellation before commit publishes no effect or root, while cancellation of later content leaves the committed directory and restores the enclosing cwd | | WF27 | A denied write | The live performer publishes the ordinary sanitized `permission-denied` refusal in the phase it stopped at, the current root and the Workspace are the ones it started with, and a read in the same run is still permitted — the class each operation asked for is what decided it | | WF28 | A completed effect under a narrower policy | Replay restores the recorded outcome, asks no permission at all and enters no performer, and appends no journal event | | WF29 | Unrecorded work | After an interrupted run, the continuation's one unrecorded write asks current policy and is refused, while the recorded write ahead of it neither asks nor reaches the filesystem | @@ -13005,13 +13005,13 @@ Defined in §8.1. | EP9 | A superseded request | Delegating a request a later `withOptions()` replaced fails | | EP10 | Another loaded copy | Middleware installed through an independently constructed descriptor of the Api's name inspects, transforms and delegates | | EP11 | Capture precedes installation | Admissions are copied before `install()` runs | -| EP12 | Every admission, in order | Each captured admission runs, in capture order, on the retained history | +| EP12 | Every admission, in order | Each captured admission runs, in capture order, on the saved history | | EP13 | One refusal stops everything | A refusing admission prevents every later admission, `ReplayGuard`, terminal reuse, preparation, `Execution.document`, authored work and any append | | EP14 | No ambient channel | Rebuilding the obsolete `executablemd.core.journal-admission` context and setting it to `[]`, before and during the invocation, removes no captured admission | | EP15 | Precedence over an existing failure | A completion policy is not consulted when the document already failed, and cannot replace its failure | | EP16 | Additive against a success | A completion policy still turns a successful document into a failure | | EP17 | First failure wins | With two policies, the first to report a failure is the result and the second is never consulted | -| EP18 | An immutable history | An admission that tries to set `length`, replace an index, delete a member, reverse or splice the retained history is ineffective or refused; the next admission sees the original, the completed replay stays completed, and nothing is appended | +| EP18 | An immutable history | An admission that tries to set `length`, replace an index, delete a member, reverse or splice the saved history is ineffective or refused; the next admission sees the original, the completed replay stays completed, and nothing is appended | | EP19 | A nested live foreign request | A nested invocation handed its caller's still-live request is refused as another execution's; both invocations then settle on their own | | EP20 | Concurrent live requests | Two invocations held at a barrier until both requests exist and neither is consumed each refuse the other's, and each still runs its own document | | EP21 | The exported default | Calling `Execution.operations.execute` directly with a live request refuses and consumes nothing; the request still settles its own invocation | @@ -13048,9 +13048,9 @@ Defined in §8.1. | DP17 | Cancellation | Cancelling while document policy is suspended keeps structured teardown: finalizers run exactly once and the result is a cancellation, not an ordinary policy failure | | DP18 | Capture precedes installation | `prepare` is read exactly once, by value, before any `install()` runs; replacing it from inside `install()` changes nothing | | DP19 | Every preparation, in order | Each captured preparation runs, in installation order | -| DP20 | A refusal keeps what was prepared | A refusing preparation stops every later preparation, all document policy, the root import and authored work; the durable effect the first preparation completed stays retained, and the journal holds exactly that Yield and the terminal recording the refusal. Resuming that history with only the terminal removed re-enters the first preparation, restores its effect with no executor call, refuses again, runs nothing later, and leaves the retained Yield singular | +| DP20 | A refusal keeps what was prepared | A refusing preparation stops every later preparation, all document policy, the root import and authored work; the durable effect the first preparation completed stays saved, and the journal holds exactly that Yield and the terminal recording the refusal. Resuming that history with only the terminal removed re-enters the first preparation, restores its effect with no executor call, refuses again, runs nothing later, and leaves the saved Yield singular | | DP21 | Admission, then preparation | Admission runs before preparation, which runs before document policy and the root import | -| DP22 | Replay of durable preparation | A completed terminal replay enters no preparation and executes no effect and appends nothing; a partial continuation re-enters preparation and restores its effect from the retained Yield without calling the executor again | +| DP22 | Replay of durable preparation | A completed terminal replay enters no preparation and executes no effect and appends nothing; a partial continuation re-enters preparation and restores its effect from the saved Yield without calling the executor again | | DP23 | Replaying a pre-root refusal | An identical execution reads the recorded refusal back, entering no preparation, no policy, no import and no authored work, and appending nothing | | DP24 | A different root document | A pre-root terminal is refused for a different inline source, a different target, and a different path, before terminal reuse | | DP25 | Binding data that fails closed | A terminal whose binding is missing, not an object, `null`, short a member, carrying an extra member, mistyped, or duplicated by a second recorded completion is never reusable | @@ -13069,7 +13069,7 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §3. | WFC2 | Reuse | A compatible reuse of an explicit id addresses the same run; an incompatible one is refused, publishes no status and leaves the stored run as it was | | WFC3 | Properties | Generated `--props-*` arguments and their help come from the pinned definition; every property form is refused on `resume` | | WFC4 | Pinned bytes | An uncommitted working-tree edit does not reach the run: the committed document executes | -| WFC5 | Resume | A resume names only a run and finds its definition through retained metadata; a document argument is refused | +| WFC5 | Resume | A resume names only a run and finds its definition through saved metadata; a document argument is refused | | WFC6 | Failure | A failing document exits 1 and retains `failed`; a compatible reuse replays that failure. The component search path is empty, so a component beside the definition never resolves | | WFC7 | Storage | A missing run and an unreadable database are reported without a status line, and the database is left byte-identical | | WFC8 | Grammar | A missing or unknown subcommand, a missing target, a third argument, `--id` on resume, an agent option and an inline document are each refused | @@ -13080,7 +13080,7 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §3. | WFC15/WFC16 | Establishment | A declared component the commit does not hold, one that is not Markdown the engine can read, and one that names a directory each refuse before a run id is reported | | WFC17 | Bundle reuse | A compatible reuse addresses the same run; a committed change to one component refuses as `definition`, publishes no status, exposes no source or path, and leaves the history byte-identical | | WFC18 | Resume from the tree | A resume reaches every stage after the root and every component are deleted from the checkout | -| WFC19 | Unreachable bundle | A resume whose retained repository is gone is refused, publishes no status, and leaves the lifecycle records exactly as they were | +| WFC19 | Unreachable bundle | A resume whose saved repository is gone is refused, publishes no status, and leaves the lifecycle records exactly as they were | ### Tier WLI — Immutable lifecycle inspection @@ -13093,25 +13093,25 @@ the production Deno adapter, on real run files. | WLI2 | An absent run | Reported as absent, and no database is created by having looked | | WLI3 | Reading changes nothing | `status`, `list` and `history` leave every byte, row and file mode as they found them | | WLI4 | List order and namespace | Newest update first, and only the `.sqlite` namespace is enumerated | -| WLI5/WLI5b/WLI5c | One bad candidate fails the list | A foreign candidate, a healthy database at another run's name, and a retained id no reader can see each fail the whole request rather than returning the healthy subset | -| WLI6 | History is what was retained | Every event with its exact id, Workspace root and authored source | +| WLI5/WLI5b/WLI5c | One bad candidate fails the list | A foreign candidate, a healthy database at another run's name, and a saved id no reader can see each fail the whole request rather than returning the healthy subset | +| WLI6 | History is what was saved | Every event with its exact id, Workspace root and authored source | | WLI7 | An unparseable source | A present source position that does not parse makes that entry unreadable rather than the run | | WLI8 | Inspection reaches nothing | A partial run is inspected without reaching storage, Git or the journal | | WLI9 | Ids are not patterns | An id holding `*` or `?` addresses exactly its own run | | WLI10 | A crashed run reports its committed state | A run whose host was killed mid-transaction reports the state it committed — not the record change, deletions or insertions that transaction held — and its database and journal are byte-for-byte what the crash left, still refusing a direct read-only reader with `SQLITE_READONLY_ROLLBACK` | | WLI11 | Discovery is still complete | A healthy run and two crashed ones are listed together in the order the same runs were answered in while all three were readable, which is still newest update first with the run id breaking ties; at most one private copy exists at a time | | WLI12 | The pair is copied under coordination | A real second process opening the same database through the production registry cannot finish its first read while inspection holds coordination with the pair copied; and an owner that recovers the source first is read as an ordinary run, with nothing copied | -| WLI13 | Recognition survives recovery | Four crashed candidates — an undeclared schema object, a run under another run's name, a retained row that is not the timestamp its column claims, and a current Workspace root the run does not retain — each answer after recovery with the identical refusal, class and message the same damage produces while the candidate is still readable; none is folded into the recovery refusal, none quotes a retained value or the removed copy, and each still fails the whole list | +| WLI13 | Recognition survives recovery | Four crashed candidates — an undeclared schema object, a run under another run's name, a saved row that is not the timestamp its column claims, and a current Workspace root the run does not retain — each answer after recovery with the identical refusal, class and message the same damage produces while the candidate is still readable; none is folded into the recovery refusal, none quotes a saved value or the removed copy, and each still fails the whole list | | WLI14 | Only the exact condition recovers | `SQLITE_READONLY_ROLLBACK` alone enters recovery; the primary readonly code and every other extended readonly code — including ones carrying the identical message — do not | -| WLI15 | A recovery that cannot finish | A sidecar that cannot be opened, a scratch directory gone before the copy, and a copy that is not a database each refuse with the typed inspection-recovery error, an undefined `scratchPath`, no residue, no SQLite wording and no cause, and fail a list whole. Cancellation with successful cleanup stays Effection control flow and leaves no copy. A real removal refusal is the authoritative outcome however the call is ending — answered, cancelled before the answer, or cancelled after it while the removal itself is suspended — carrying the retained database and the directory that remains, which is really there. Every branch leaves the retained pair byte-identical and still hot | +| WLI15 | A recovery that cannot finish | A sidecar that cannot be opened, a scratch directory gone before the copy, and a copy that is not a database each refuse with the typed inspection-recovery error, an undefined `scratchPath`, no residue, no SQLite wording and no cause, and fail a list whole. Cancellation with successful cleanup stays Effection control flow and leaves no copy. A real removal refusal is the authoritative outcome however the call is ending — answered, cancelled before the answer, or cancelled after it while the removal itself is suspended — carrying the saved database and the directory that remains, which is really there. Every branch leaves the saved pair byte-identical and still hot | | WLI17 | An open connection owns the pair | A write-capable connection opened while the run was healthy keeps the pair when a later crash leaves a hot journal: inspection creates no copy and the journal survives untouched while that connection is open, and only closing it lets inspection recover a copy and answer | -| WLI16 | The common path costs nothing, and recovery buys no ownership | A clean run is read directly, creating no private copy, no coordination sidecar and no recovery step. A crashed one recovers under refusing storage and Git probes without reaching either, takes no executor-lock sidecar, receives nothing from recovery coordination, and leaves the retained pair byte-identical | +| WLI16 | The common path costs nothing, and recovery buys no ownership | A clean run is read directly, creating no private copy, no coordination sidecar and no recovery step. A crashed one recovers under refusing storage and Git probes without reaching either, takes no executor-lock sidecar, receives nothing from recovery coordination, and leaves the saved pair byte-identical | ### Tier WFI — What a run hands to canonical core | # | Test | Verify | |---|------|--------| -| WFI1 | One installation, one execution | A run reaches core as exactly one `ExecutionInstallation` carrying its retained-run admission and its `prepare` hook, through exactly one execution — not an `executeInstalled()` followed by an `execute()` | +| WFI1 | One installation, one execution | A run reaches core as exactly one `ExecutionInstallation` carrying its saved-run admission and its `prepare` hook, through exactly one execution — not an `executeInstalled()` followed by an `execute()` | | WFI2 | Completed replay attaches nothing | A live pass attaches the run's Workspace; a completed replay of the same journal attaches none | | WFI3 | Terminal resume refusal | A `failed` or `cancelled` run refuses `resume` with exit 1, and the substituted Git Api — which records every operation — is never invoked: no definition is retrieved, no Workspace attached, no executor run, no status line published, and status, stop reason, definition, props, retrieval metadata, every document-execution record and every journal entry under its own id remain structurally unchanged | | WFI4 | Admission controls | `completed` and `interrupted` runs are both admitted and both reach the executor; only the completed one is spared a Workspace | @@ -13140,7 +13140,7 @@ remote and a real `git`. | WGP4 | What cannot be proved | A destination naming a commit the authenticated source cannot read conflicts without fetching it and without asking about its ancestry; an ancestry proof that answers neither ancestry nor divergence is a boundary failure that publishes no outcome and moves nothing | | WGP5 | Preserved classifications | An unreachable observation is still temporary unavailability and an undecidable one is still ambiguity, and neither performs | | WGP6 | An uncertain completion | A push that lands while reporting failure earns exactly one more observation, is accepted only because the destination equals the commit, and mutates the remote no further; a killed process's publication is adopted by the next execution | -| WGP7 | What an advance never does | No force, force-with-lease, upstream establishment, reset, merge, rebase, fetch, checkout mutation or second push attempt, in the command trace or the retained checkout configuration | +| WGP7 | What an advance never does | No force, force-with-lease, upstream establishment, reset, merge, rebase, fetch, checkout mutation or second push attempt, in the command trace or the saved checkout configuration | | WGP8 | Pull-request evidence | A numbered `` update accepts this iteration's Push record even after an earlier publication of the same branch; the run's last relevant publication decides, so a history holding only an earlier one, and one whose last record names another commit, are both conflicting without the Git host being contacted | | WGP9 | Compatibility | An `ancestor`/`performed` record and the two shapes written before relations existed all replay without reaching `init`, `ls-remote`, `merge-base`, `fetch` or `push`; a relation beside a proven absence, an unknown relation, an undeclared pre-state member, an adoption carrying a relation and a performance over an unattested predecessor each fail the replay closed | | WGP10 | Resuming a branch published twice | A run that publishes A, commits B and publishes B resumes after only its root `Close` is removed: both Push records restore in order with their own source commits, the run stays at B, and no `init`, `ls-remote`, `merge-base`, `fetch` or `push` runs — each completed Push names its request from the Workspace root its own journal event was appended against, and reading that root publishes nothing and moves no frontier | @@ -13153,14 +13153,14 @@ against the production Deno adapter, on real run files. | # | Test | Verify | |---|------|--------| -| WAD1 | Correlation | A value is retained only for the exact wait it names; a wait this run is not at, and a run that holds no such wait, are refused with nothing written | -| WAD2 | The retained schema judges | Every value the response schema refuses is refused before persistence, and a value it describes still goes in afterwards | +| WAD1 | Correlation | A value is saved only for the exact wait it names; a wait this run is not at, and a run that holds no such wait, are refused with nothing written | +| WAD2 | The saved schema judges | Every value the response schema refuses is refused before persistence, and a value it describes still goes in afterwards | | WAD3 | The secret gate | A credential-shaped answer is refused with neither the value nor the match in the diagnostic; `--no-secret-detection` retains the same value | -| WAD4 | Delivery is not execution | Status, stop reason, document-execution records and retained history are byte-for-byte what the suspension left | +| WAD4 | Delivery is not execution | Status, stop reason, document-execution records and saved history are byte-for-byte what the suspension left | | WAD5 | No executor lock | A delivery succeeds while a live workflow executor holds the run's lock | | WAD6/WAD7/WAD8 | Refusals | A duplicate, an answer already spent, a completed run, a cancelled run and a run nothing is stored for are each refused, and none of them writes | | WAD9 | The resume spends it | One `suspension_answer` event naming the wait, the pending state consumed, and `suspendFor()` returning the delivered value | -| WAD10 | Replay | A later resume restores the answer from its retained event, reaching no live controller, publishing no second event and consuming nothing again | +| WAD10 | Replay | A later resume restores the answer from its saved event, reaching no live controller, publishing no second event and consuming nothing again | | WAD11/WAD12 | One transaction | A journal insertion that fails, and a consumption that fails, each leave the answer pending and no answer event; the next resume publishes exactly one | ### Tier WAP — The strict workflow Agent profile @@ -13177,9 +13177,9 @@ Defined in [ACP client](./acp-client-spec.md) §The workflow Agent profile. | WAP6 | Nothing echoed | No tool title, raw input, path or command reaches the failure message or its stack | | WAP7 | Sibling Sessions | Two `` sites in one document place two engine identities the middleware cannot touch while its rename reaches the provider; a placement kept from the first site and routed for the second refuses before the provider places anything | | WAP8 | Placement only | A fresh `` under this profile ensures nothing, opens no handle, writes no provider record and retains nothing; the host is asked to place and is not asked to retain | -| WAP9 | Retention follows acceptance | Nothing is retained until the backend accepts the session's first turn, and the ordered log is ensure, turn start, adapter acceptance, the provider's asserting save, then the host's retention — with the mapping absent at every earlier point | +| WAP9 | Retention follows acceptance | Nothing is saved until the backend accepts the session's first turn, and the ordered log is ensure, turn start, adapter acceptance, the provider's asserting save, then the host's retention — with the mapping absent at every earlier point | -### Tier WSL — Retained workflow Agent sessions +### Tier WSL — Saved workflow Agent sessions Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8.5. @@ -13191,7 +13191,7 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8.5. | WSL9 | An unaccepted first turn | A first turn the backend never accepted leaves the construction route standing, the provider record non-asserting, no mapping, and no identity on the `Session` the document holds; the same value retried creates fresh backend state and establishes once | | WSL10 | Promotion order | The provider's asserting record is saved before the host is asked to retain anything, so an interruption between them leaves exactly one canonical assertion for the next attachment to reconcile and commit | | WSL4 | Refusals | A missing assertion, a different value, a different kind, a changed agent, a changed provider, a changed policy and more than one assertion each refuse | -| WSL5 | Reattachment | A matching assertion reattaches the retained mapping unchanged | +| WSL5 | Reattachment | A matching assertion reattaches the saved mapping unchanged | | WSL6 | The run's transaction | The mapping commits in the run's own transaction and reads back per Session site; a transaction that fails commits none | | WSL7 | Directories | Owning the sidecar allocates none of it; every attachment gets an empty directory, and neither it nor the host directory outlives the attachment | | WSL8 | Deletion | Deletion reports and removes both categories, and a live run keeps them | @@ -13213,13 +13213,13 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8.4. | # | Test | Verify | |---|------|--------| -| GXC1 | The selection | The trusted composition table is present under every selection. Omitting `allow` and stating `read` produce one identical retained policy; a mixed selection is retained in canonical class order, with composition before the read and write classes and the selected names in canonical order inside each | -| GXC2 | Unstateable policy | An empty selection, one class twice, a selected class with no table or an empty one, and composition/effect tables holding one name with overlapping forms or distinct definitions each fail before the candidate is parsed — with a deliberately unparseable candidate, no `generated_xmd` record and nothing of the candidate retained | -| GXC3–GXC4 | Name and form | A self-closing and a paired `` in one fragment resolve to the read and the write identity and are retained with their forms; the opposite form under a single-class selection is refused with no read and no write | +| GXC1 | The selection | The trusted composition table is present under every selection. Omitting `allow` and stating `read` produce one identical saved policy; a mixed selection is saved in canonical class order, with composition before the read and write classes and the selected names in canonical order inside each | +| GXC2 | Unstateable policy | An empty selection, one class twice, a selected class with no table or an empty one, and composition/effect tables holding one name with overlapping forms or distinct definitions each fail before the candidate is parsed — with a deliberately unparseable candidate, no `generated_xmd` record and nothing of the candidate saved | +| GXC3–GXC4 | Name and form | A self-closing and a paired `` in one fragment resolve to the read and the write identity and are saved with their forms; the opposite form under a single-class selection is refused with no read and no write | | GXC5–GXC6 | Admission | No admitted name — `` in either form, the versioned paired `` or the self-closing `` — is answered by a same-name repository component, the dotted one included; and Git push, pull request, issue, repository, glob and an executable block are outside the tables whatever the selection | | GXC7 | Ordinary composition | A write-only fragment renders nothing unless it authors output; a mixed fragment binds a read locally and renders exactly the value it explicitly selects | | GXC8 | Preflight | An unadmitted sibling after an admitted write, an unadmitted child under an admitted parent, and an unadmitted form after an admitted write each perform no write at all | -| GXC10 | The form at the invocation | An admitted read still performs exactly its read and an admitted write still performs exactly its write, under a `Component.hasContent` handler outside the generated expansion that lies consistently *and* under one answering `[false, true]` / `[true, false]` — with the retained identity and form unmoved, the read's ordinary binding/output behavior intact, and the file bytes proving which effect ran. An admitted read beside each of them reports the chain's answer and consumes exactly one, so every case proves the handler installed and answering before it proves the element ignored it — an inert handler, and an element that consulted the chain and took the next answer, both fail. An invocation the evaluator did not receive is refused with no provider call, and a handler that observes and delegates leaves both forms running through their ordinary providers | +| GXC10 | The form at the invocation | An admitted read still performs exactly its read and an admitted write still performs exactly its write, under a `Component.hasContent` handler outside the generated expansion that lies consistently *and* under one answering `[false, true]` / `[true, false]` — with the saved identity and form unmoved, the read's ordinary binding/output behavior intact, and the file bytes proving which effect ran. An admitted read beside each of them reports the chain's answer and consumes exactly one, so every case proves the handler installed and answering before it proves the element ignored it — an inert handler, and an element that consulted the chain and took the next answer, both fail. An invocation the evaluator did not receive is refused with no provider call, and a handler that observes and delegates leaves both forms running through their ordinary providers | | GXC9 | Continuation | A widened class selection, a widened admitted form, a replaced write identity and an added write identity each refuse the continuation and write nothing; the unchanged policy resumes and performs the write; and a read-only admission survives a changed write selection it never reached | ### Tier GX — Generated continuation in the owning expansion @@ -13228,16 +13228,16 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §§8.4 and 9. | # | Test | Verify | |---|------|--------| -| GX15–GX16 | Retained decision | A partial replay restores the admission and performs no second request; a completed replay restores the root result and repeats no observation | -| GX18a–GX18b | The offered sequence | Admission, the nested generated read or durable write, a later parent durable marker and the root import are one exactly ordered offered sequence; a partial continuation stating the original roots plus one new retained root, with that new root current, offers the same sequence, and the live Fetch/write/marker executors each ran once across both executions | +| GX15–GX16 | Saved decision | A partial replay restores the admission and performs no second request; a completed replay restores the root result and repeats no observation | +| GX18a–GX18b | The offered sequence | Admission, the nested generated read or durable write, a later parent durable marker and the root import are one exactly ordered offered sequence; a partial continuation stating the original roots plus one new saved root, with that new root current, offers the same sequence, and the live Fetch/write/marker executors each ran once across both executions | | GX18c–GX18d | Controls | An empty and a rendered-only fragment preserve output across partial replay and invent no nested durable effect | -| GX22, GX22b | Progression | The run's own root addition with the added root current, and standing on another root it already retained, each resume and reach the generated component | +| GX22, GX22b | Progression | The run's own root addition with the added root current, and standing on another root it already saved, each resume and reach the generated component | | GX22c–GX22d | Root loss | Losing a non-selected admission root and losing the admission's selected root each refuse with the fixed diagnostic before the component runs, the live component count unchanged | | GX14b–GX14c | One ceiling per entry | Two admitted reads each perform exactly their own admitted request, and neither entry's limit admits the other's: the run keys its ceiling table by the entry itself rather than by anything describing one | | FE18/GX21z | The current record restores | The record this build writes resumes against the entry it was admitted under and performs the work it authorized once; the same URL behind an updated implementation resumes, because between executions that is a newer build of the component the admission named rather than a changed grant; and a recorded refusal replays as the refusal it recorded | | FE18/GX21y | One closed record shape | The untagged #369 record and the tagged structural-identity record are each refused as unreadable history. So are a marker this build does not have, legacy top-level roots, an unknown policy, row, result or named member, a row with no form, an unknown form, effect or kind, and an identity that is relative, a look-alike origin, a structural record, an array or null — each with the admitted read never reaching the provider, against the current record as the positive control | | EP18–EP20 | One name, one implementation | A name selected in both spellings is resolved by one lookup and captured as one shared definition and schema, each spelling reading its own identity and effect off that definition and landing in the table its owner offers it for; an answer offering no generated use of a selected spelling refuses at capture rather than letting a selection describe a grant the implementation lacks; and both spellings offered as reads land in the read table together. In `evaluation-profile.test.ts` | -| GOM1–GOM4 | The owner states what its spellings are offered as | A registration's generated map survives registration copied and frozen; an omitted or empty map registers and offers no spelling; a relative, look-alike or non-URL identity, an unknown effect, a key that is not an authored form, a malformed value, a non-map and a spelling the declaration does not accept each refuse, and a refused batch installs nothing. The shipped owners state exactly the owner table, ``'s two spellings differ on one definition, `` stays composition and `` offers nothing. A shared fixed definition is frozen through its nested metadata, so a fresh read sees the original contract. A component a host declares to an execution keeps its offer through capture: the built implementation runs the spelling it offered under the URL and effect the admission retained, the offer is read exactly once so an accessor's later reading reaches neither construction nor the journal, an offer replaced from inside `install()` replaces nothing, and an unusable one refuses the execution before an implementation is built, a provider is asked or anything is written down. In `generated-owner-metadata.test.ts` | +| GOM1–GOM4 | The owner states what its spellings are offered as | A registration's generated map survives registration copied and frozen; an omitted or empty map registers and offers no spelling; a relative, look-alike or non-URL identity, an unknown effect, a key that is not an authored form, a malformed value, a non-map and a spelling the declaration does not accept each refuse, and a refused batch installs nothing. The shipped owners state exactly the owner table, ``'s two spellings differ on one definition, `` stays composition and `` offers nothing. A shared fixed definition is frozen through its nested metadata, so a fresh read sees the original contract. A component a host declares to an execution keeps its offer through capture: the built implementation runs the spelling it offered under the URL and effect the admission saved, the offer is read exactly once so an accessor's later reading reaches neither construction nor the journal, an offer replaced from inside `install()` replaces nothing, and an unusable one refuses the execution before an implementation is built, a provider is asked or anything is written down. In `generated-owner-metadata.test.ts` | ### Tier WGAC — Generated `` effects and the `` boundary @@ -13249,17 +13249,17 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8.4. | WGAC13 | The deletion entry | The pinned `` entry is admitted for the self-closing form alone, under `https://executable.md/components/file/delete` as a write, and carries the exact definition object core holds rather than one shaped like it | | WGAC2 | Preflight | A paired `` — empty or not — and an unadmitted component anywhere in a mixed fragment each perform zero reads and zero writes, and nothing of the source reaches the record | | WGAC3 | Closed schema | `` refuses content, refuses every prop but `source` and `allow`, and refuses a missing `source` | -| WGAC4 | Host-owned roots | The stated ceiling is the run's retained roots and the root it is on, following the run as it moves, in a deterministic order; generated source cannot reach the component even while it is live | +| WGAC4 | Host-owned roots | The stated ceiling is the run's saved roots and the root it is on, following the run as it moves, in a deterministic order; generated source cannot reach the component even while it is live | | WGAC5 | Host-owned requests | An empty ceiling admits no `` at all, an exact one admits only the request it names, and neither performs anything else | | WGAC6 | Distinct sites | Two `` sites keep distinct durable names under a component that rebinds what a component can | | WGAC7 | The import boundary | Through public `importComponent` middleware: calling the implementation with an invocation it built, with the first site's routed at the second, with a live content-bearing parent's routed into the sites inside it, by keeping the declared implementation and running it at another element's invocation, and by keeping one attachment's implementation — with that attachment's whole registration record answered for the name inside a second, simultaneously live attachment — and running it at the second attachment's own `` site, are each refused. Nothing is admitted and no request performed under an identity the author did not write at that site, and neither run's database receives a record the other's expansion named: a redirected registry answer carries behavior, never the claimant this execution delivered. Honest delegation, forwarding the genuine invocation, admits each site under its own name, nested or not; and an interrupted run resumes into each site's own record — a completed read restores and the first interrupted request runs | | WGAC8 | Ordinary output | Evaluate emits exactly what the generated fragment renders. Bound component results emit nothing unless the fragment explicitly renders them, and no read is copied into an implicit result collection | -| WGAC9 | The class selection | Exact core Json remains available from the trusted composition table. An empty, repeated, unknown or non-array `allow` is refused before any admission; `read`, `write` and both in either order are admitted and retained in canonical order; omitting it retains exactly composition plus read, and the writing spellings are not in that policy at all | -| WGAC10 | The standard writing spellings | An admitted paired `` beneath a generated `` first recursively creates the directory through one ordinary `workspace_file` effect, then lands the file at the nested logical path through another; the retained policy holds all three standard rows with their spellings, in the canonical order the selection is admitted in — paired `` under `https://executable.md/components/dir/ensure`, core's paired `` under `.../file/write`, then core's self-closing `` under `.../file/delete`, each as a `write` — and a `` and a self-closing `` are each refused under the same selection with no effect | -| WGAC14 | The admitted deletion | A write-only fragment holding one self-closing `` removes the file from the run's own Workspace, read back through the run's own transaction; it performs exactly one further `workspace_file` effect, whose outcome is `{ kind: "deleted" }`; it is retained under `@executablemd/core#File.Delete` in the self-closing form; and it renders no synthetic receipt | -| WGAC15 | Preflight covers the deletion | A fragment placing an admitted `` first and an executable code block second is refused for the block — the class proving the deletion was admitted, since a refusal names no name — and the file remains with no deletion effect performed or retained | +| WGAC9 | The class selection | Exact core Json remains available from the trusted composition table. An empty, repeated, unknown or non-array `allow` is refused before any admission; `read`, `write` and both in either order are admitted and saved in canonical order; omitting it retains exactly composition plus read, and the writing spellings are not in that policy at all | +| WGAC10 | The standard writing spellings | An admitted paired `` beneath a generated `` first recursively creates the directory through one ordinary `workspace_file` effect, then lands the file at the nested logical path through another; the saved policy holds all three standard rows with their spellings, in the canonical order the selection is admitted in — paired `` under `https://executable.md/components/dir/ensure`, core's paired `` under `.../file/write`, then core's self-closing `` under `.../file/delete`, each as a `write` — and a `` and a self-closing `` are each refused under the same selection with no effect | +| WGAC14 | The admitted deletion | A write-only fragment holding one self-closing `` removes the file from the run's own Workspace, read back through the run's own transaction; it performs exactly one further `workspace_file` effect, whose outcome is `{ kind: "deleted" }`; it is saved under `@executablemd/core#File.Delete` in the self-closing form; and it renders no synthetic receipt | +| WGAC15 | Preflight covers the deletion | A fragment placing an admitted `` first and an executable code block second is refused for the block — the class proving the deletion was admitted, since a refusal names no name — and the file remains with no deletion effect performed or saved | | WGAC11 | Explicit composition | A fragment binds read results locally, composes selected values through Json, and renders no value or mutation receipt it did not explicitly render | -| WGAC12 | Committed mutations | A completed replay of a write-enabled document journals nothing new, performs no second mutation, and leaves the retained content | +| WGAC12 | Committed mutations | A completed replay of a write-enabled document journals nothing new, performs no second mutation, and leaves the saved content | | WGAC16 | Bundled continuation | `` inside a committed bundled Markdown component, then a parent `` write and the real `` outside it, for a generated read and a generated write alike: the real start suspends holding the exactly ordered `generated_xmd → nested workspace_file → parent workspace_file → suspension_request` subsequence; answer delivery and the completed resume leave the journal counts, that subsequence, the Workspace root-publication count and the authoritative current root unchanged; and the delivered value reaches the document after the wait. `API.Files` component calls are not evidence here — re-expansion legitimately enters that boundary before the durable effect restores | | WGAC18 | A generated question is the host's to offer | This host's generated selection names no question, so a fragment writing `` under `allow={["write"]}` is refused for a component it did not offer, with nothing asked and nothing performed; a host that offers the name admits the same fragment. A host whose own replacement answers for `Elicit` and offers no generated use of the paired form refuses setup rather than reaching core's default | | WGAC17 | Directory mutation permission | `allow={["write"]}` admits the paired `` under the identity core's own declaration offers it as, and intentionally authorizes its persistent recursive creation; a continuation retaining another identity for that spelling refuses before generated execution and creates nothing, without publishing which identity moved, while an unchanged current admission replays without a second ensure | @@ -13273,12 +13273,12 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8. | WAL1 | The loop | Two observations then a proposal across three turns in one provider session, each observation reaching the next turn, with no Workspace or caller path in provider setup | | WAL2 | Completed replay | The same document over its own journal restores identical output, journals nothing new, and never enters the provider | | WAL3 | Partial replay | An interrupted run resumes, asks only for the turn it never finished, repeats no committed observation or read, and reattaches the same session | -| WAL4 | Exhaustion | Reaching the authored bound fails at the document's own final gate, with every completed turn and admission retained | +| WAL4 | Exhaustion | Reaching the authored bound fails at the document's own final gate, with every completed turn and admission saved | | WAL5 | Inert proposal | A mutation-shaped proposal comes back exactly, is admitted nowhere, and performs no file effect | -| WAL6 | Denied tool | A denied native tool becomes the retained Prompt failure, carrying nothing the request named | +| WAL6 | Denied tool | A denied native tool becomes the saved Prompt failure, carrying nothing the request named | | WAL7 | No Agent | A workflow naming no Agent enters no runtime and allocates no provider-session sidecar | | WAL8 | ACP-only Claude | The profile states both ordinary-run native capability sets empty, so an agent `xmd run` would own machine-wide is served here over ACP alone. A provider handed a coordinator, a route store and an executable observer under those empty sets reaches none of them for a Claude prompt | -| WAL8 | An observed response reaches the next turn | An admitted ``'s complete retained response — name, status, headers and body — is rendered into the next `` with ``, and a completed replay restores it without asking the server or the agent again | +| WAL8 | An observed response reaches the next turn | An admitted ``'s complete saved response — name, status, headers and body — is rendered into the next `` with ``, and a completed replay restores it without asking the server or the agent again | | WAL9 | Authored approval | The same proposal under the production profile: the approved branch reaches ``, records one admission naming both write identities and the class, and leaves the change where an ordinary read in the run's Workspace finds it; the refused branch never expands the element, so no admission and no file effect exist | ### Tier WFX — A killed run resumes from its frontier @@ -13307,25 +13307,25 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8. | IS5 | Compiled binary, end to end | The guide's lifetime narrative, run by `xmd test` with no JavaScript in the document | | IS6 | Contextual, and composable | The operation stays exported, delegable and overridable, and both it and `invocation.hasContent()` stay observation — cross-copy readable, and neither an effect permission. What decides an effect-selecting branch is engine-owned invocation-form dispatch (§5.6), which no handler and no object a caller passes takes part in | -### Tier RT — Retained resources +### Tier RT — Saved resources | # | Test | Verify | |---|------|--------| | RT1 | Captured value reaches a sibling | `as` binds the value and a downstream sibling resolves it while the resource is still live | -| RT2 | Outlives the child invocation | A later sibling invocation starts and stops entirely inside the window where the retained resource is alive | -| RT3 | Released on site success | `start:retained, stop:retained` when the site scope completes normally | +| RT2 | Outlives the child invocation | A later sibling invocation starts and stops entirely inside the window where the saved resource is alive | +| RT3 | Released on site success | `start:saved, stop:saved` when the site scope completes normally | | RT4 | Released on site error | The failure propagates and the resource is still released | | RT5 | Released on site cancellation | Halting the site scope while the resource is live releases it | -| RT6 | Nested sites unwind leaf-first | A resource retained inside a component's content stops before that component's own | +| RT6 | Nested sites unwind leaf-first | A resource saved inside a component's content stops before that component's own | | RT7 | Retention is opt-in | A component that does not call `retain()` keeps invocation lifetime | | RT8 | No invocation-site scope | `retain()` reports the missing scope instead of falling back to invocation lifetime | | RT8b | Missing site is authoritative | An inherited `retain` provider never answers for an invocation that has no site of its own | -| RT9 | Root retention | A resource retained at the root outlives every later sibling in the document | -| RT10 | Halting mid-expansion | Halting while a later block suspends still releases what an earlier element retained | -| RT15 | Retention under partial replay | The durable effect replays while the retained resource is re-established on each execution that runs | +| RT9 | Root retention | A resource saved at the root outlives every later sibling in the document | +| RT10 | Halting mid-expansion | Halting while a later block suspends still releases what an earlier element saved | +| RT15 | Retention under partial replay | The durable effect replays while the saved resource is re-established on each execution that runs | | RT16 | Eval cannot retain | An eval block's `retain()` is refused, and the block produces no value | | RT17 | Compiled binary, end to end | The guide's standalone scenario: the resource is still live for a later sibling, with no JavaScript in the document | -| RT18 | Site isolation | A factory that sets a context value and installs middleware: the resource is retained, and a later sibling still observes the caller's own | +| RT18 | Site isolation | A factory that sets a context value and installs middleware: the resource is saved, and a later sibling still observes the caller's own | ### Tier P — Eval binding interpolation @@ -13370,7 +13370,7 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8. | R5 | Durable export collides with a service | Live execution and valid partial replay diagnose the block before constructing an eval effect; a later durable effect executes or restores in alignment, and no eval `Yield` appears | | R6 | Ephemeral export collides with durable state | The block is rejected before execution and publishes no partial export | | R7 | Ephemeral update of a live binding | A later ephemeral block may atomically replace an existing live name | -| R8 | Incompatible retained eval history | Genuine history from an earlier component definition fails fatally before restoring its now-incompatible eval result; effect-level mismatch and immediate root termination both append neither replacement `Yield` nor root `Close`, leave the retained prefix byte-for-byte unchanged, and permit the compatible definition to replay it | +| R8 | Incompatible saved eval history | Genuine history from an earlier component definition fails fatally before restoring its now-incompatible eval result; effect-level mismatch and immediate root termination both append neither replacement `Yield` nor root `Close`, leave the saved prefix byte-for-byte unchanged, and permit the compatible definition to replay it | | R9 | `when` accessible in eval block | `yield* when(fn)` retries until fn succeeds | | R10 | `when` retries on throw | Inner function throws twice, then succeeds → `when` resolves | | R11 | `when` propagates timeout | Inner function never succeeds → `when` throws after limit | @@ -13396,7 +13396,7 @@ Defined in [Workflow workspaces](./workflow-workspace-spec.md) §8. | S15 | Incremental ordinary stdout | Unterminated and chunk-split ordinary bytes are forwarded before teardown, byte for byte | | S16 | Incremental handshake records | A split handshake is suppressed, duplicate supervision remains active, and invalid candidates are bounded and suppressed | | S17 | Service teardown failures | A lone observable process teardown failure becomes `ServiceTeardownError`; an active execution failure remains first in the invocation aggregate | -| S18 | Projected failure cleanup | A prompt projected-content failure tears down both retained real services and starts neither again | +| S18 | Projected failure cleanup | A prompt projected-content failure tears down both saved real services and starts neither again | | S19 | Compiled-binary attached-service ping-pong | A smoke document attaches two real services, closes both endpoints into ephemeral middleware, journals only the ordinary filtered `Sample` result and completes `ping→pong→ping` | ### Tier EO — eval output() function @@ -13545,7 +13545,7 @@ Identifiers match `packages/core/tests/switch.test.ts` one to one. | SW56 | Projection | A `` through `` reads the caller's bindings | | SW57 | Journal | Only the selected case's eval entry reaches the journal | | SW58 | Partial replay | From a journal prefix without the root Close, the selected effect restores, selection is rebuilt, the run continues at the next live effect and the output is reproduced | -| SW59 | Completed replay | The retained result is returned with no selector, matcher, import or block | +| SW59 | Completed replay | The saved result is returned with no selector, matcher, import or block | The shared entrypoints carry the rest: `syntax-symbols.test.ts` SY4b and SY5b freeze both symbol entries and prove no repository file or registration can @@ -13831,7 +13831,7 @@ subject. | FT1 | Refusal before transport | Mutating and unknown methods, a lowercase `get`, a relative or non-HTTP URL, a body, an unknown prop, a missing URL, a non-string header value, and two spellings of one name each refuse with request count zero | | FT2–FT4 | The normalized request | GET by default, HEAD verbatim, lowercase header names in lexicographic order with values untouched | | FT5–FT9 | The bound | Accepted spellings reach the provider as milliseconds; every rejected class costs no request; an explicit prop outranks `Config.timeoutFetch`; no bound at all is an absent field | -| FT10–FT13 | The retained response | One canonical JSON value, identical in the binding and the journal; a provider that rewrites its headers afterwards changes neither; a provider that cannot enumerate them is refused | +| FT10–FT13 | The saved response | One canonical JSON value, identical in the binding and the journal; a provider that rewrites its headers afterwards changes neither; a provider that cannot enumerate them is refused | | FT14 | Bodies | GET reads once, HEAD not at all — including when a body read would fail | | FT15–FT17 | What a status means | A captured non-2xx is data and the document carries on; an uncaptured 2xx renders nothing; an uncaptured non-2xx records the response and stops later executable work | | FT18–FT20 | The binding seam | `hasBinding()` answers for the invocation that asked — siblings, a nested invocation inside its caller, and two invocations live at once | @@ -13860,7 +13860,7 @@ Defined in §5.6, with the selection rule in §5.3. | CIV8 | No borrowed implementation | An implementation kept from one declared component, called at an invocation of another with that invocation's genuine issuance, is refused | | CIV9 | No borrowed execution | An implementation kept from one execution, called at another execution's own invocation of the same component, is refused | | CIV10 | No transplanted record | The same, with the first execution's whole registration record answered for that name inside the second: still refused | -| CIV11 | No shadowed registration | With `` authored in a fixture, the declared implementation is retained at its own site and run at a site whose name a nested registration shadowed: refused, and the case fails if identity is restored by authored-name equality | +| CIV11 | No shadowed registration | With `` authored in a fixture, the declared implementation is saved at its own site and run at a site whose name a nested registration shadowed: refused, and the case fails if identity is restored by authored-name equality | | CIV12 | No concurrent frame | An issuance belonging to another live invocation of the same component, in a frame of its own, is refused in this one | | CIV13 | No refused registration | A declared component whose registration is refused leaves a claimant that answers for nothing | | CIV14 | Lifetime | An issuance the engine has ended names nothing, claimed in its own frame | @@ -13935,8 +13935,8 @@ what they observe is what a person's terminal would show. | DM52 | A mark this engine did not make is nothing | A segment carrying the disposition as a field is not exact, whoever supplied it; expansion also rebuilds text segments, so such a field never reaches emission in the first place | | DM53 | The record is not reachable by name | A component that builds a context with the record's name and writes to it publishes prose | | DM54 | A spawned child's source is still source | A declared exact component rendered inside ``/`` keeps its bytes unpresented, and a prose sibling stays prose | -| DM55 | A replayed child restores what its bytes were | Each child's retained close holds its ordered `{ text, exact }` runs, and replaying that history reproduces the live output with the same provenance | -| DM56 | A close that will not read fails the run | A retained child close that is not a list of runs, or holds a member that is not one, refuses atomically and publishes nothing | +| DM55 | A replayed child restores what its bytes were | Each child's saved close holds its ordered `{ text, exact }` runs, and replaying that history reproduces the live output with the same provenance | +| DM56 | A close that will not read fails the run | A saved child close that is not a list of runs, or holds a member that is not one, refuses atomically and publishes nothing | ### Tier MDK — Declaring exact Markdown with `Markdown({…})` (§5.3) @@ -14007,7 +14007,7 @@ Plugin is the run it always was. | PL1 | The value | `Plugin(input)` returns the value it was given and adds no identity, version or capability; a Plugin with no `install` installs nothing | | PL2 | Module admission | An object with a non-empty `name` and an absent or callable `install` is admitted; a non-object, a missing, empty or non-string name, and a non-callable `install` each refuse naming the specifier and the member that failed | | PL2a | Admission returns the value | What comes back is the admitted object itself: members outside the contract survive, `install` is the same function the module exported, and calling it through admission gives it the receiver its own module gave it | -| PL3 | The flag grammar | Both spellings are read in occurrence order, only those tokens are removed, the scan stops at `--`, the original argv is retained frozen, and a missing or option-shaped value refuses before anything loads | +| PL3 | The flag grammar | Both spellings are read in occurrence order, only those tokens are removed, the scan stops at `--`, the original argv is saved frozen, and a missing or option-shaped value refuses before anything loads | | PL4 | The command a Plugin is told | Each public command reports its own name — `repl` included — and the shorthand document form reports `run`; help, `--version` and the internal worker mode install no Plugin and load no module | | PL5 | Order composes | The bundled Plugin comes first where the command's profile carries it, then the repeated `--plugin` occurrences in written order; the first Plugin installed is the outermost `Document` wrapper, and reversing the selection reverses the composition behind the prefix | | PL6 | One name, one Plugin | Two selections claiming one Plugin name refuse before the first `install()` runs, whichever modules they came from | @@ -14063,10 +14063,10 @@ user's own `~/.xmd/repositories`. | ORC6c | Context boundaries | Repository and Worktree bodies observe their checkout; `` content observes the ensured directory; the enclosing cwd is restored after success, printed failure and cancellation | | ORC6d | Repository remains selected | Entering a directory inside a selected checkout changes only cwd; every Repository identity member and provider selection remains unchanged | | ORC6e | Host persistence | An ordinary host-created directory persists after successful, failed and cancelled content, and no teardown deletion occurs | -| ORC6f | Workflow atomic ensure | Recursive creation, one durable `workspace_file` success and the resulting Workspace root commit together before content; the root retained by the effect is the authoritative current root | +| ORC6f | Workflow atomic ensure | Recursive creation, one durable `workspace_file` success and the resulting Workspace root commit together before content; the root saved by the effect is the authoritative current root | | ORC6g | Workflow refusal rollback | A target or intermediate non-directory rolls back its mutation savepoint, publishes a sanitized refusal against the unchanged root, creates no partial parent and begins no content | -| ORC6h | Workflow replay and cancellation | Completed replay restores the recorded success and retained root without ensuring again; cancellation before commit publishes neither, while later content cancellation preserves the committed directory and restores cwd | -| ORC6i | Generated write permission | `allow={["write"]}` admits the paired `` under `https://executable.md/components/dir/ensure` and authorizes persistent recursive creation; a retained entry naming another identity for that spelling refuses before generated execution and mutation | +| ORC6h | Workflow replay and cancellation | Completed replay restores the recorded success and saved root without ensuring again; cancellation before commit publishes neither, while later content cancellation preserves the committed directory and restores cwd | +| ORC6i | Generated write permission | `allow={["write"]}` admits the paired `` under `https://executable.md/components/dir/ensure` and authorizes persistent recursive creation; a saved entry naming another identity for that spelling refuses before generated execution and mutation | | ORC7 | Session placement | A Session launched in a managed Worktree receives that Git root and a distinct worktree session key; `.git`-file discovery remains the boundary | | ORC8 | Persistent lifecycle | Managed paths, metadata and working files survive normal completion, authored failure and cancellation; no teardown Git or delete command occurs | | ORC9 | Compatible reuse | A second invocation with the same immutable request reuses the same path and preserves a moved branch and a later commit while revalidating owner, origin, object format and creation commit | @@ -14080,7 +14080,7 @@ user's own `~/.xmd/repositories`. | ORC17 | Live PullRequests | Configured reviews, comments and checks reads, and a Push-authorized upsert, run under ordinary Deno; the read ceiling and the local evidence check both precede credential and network access | | ORC18 | The journal is diagnostic | The same fixture without and with `--journal` performs the same live operations once per invocation; the trace is newly created and never consumed as continuation or evidence | | ORC19 | Nested run profile | An isolated `host="run"` child receives the declarations and a fresh provider; its evidence and locks reach neither its parent nor a sibling | -| ORC20 | Retained workflow regression | Repository and Worktree replay, transactional Git, Push and pull-request history evidence, Issue effects, forks and completed replay keep their records, identities, provider call counts and native-launch refusal unchanged; directory ensure adds only its own `workspace_file` event and resulting retained root | +| ORC20 | Saved workflow regression | Repository and Worktree replay, transactional Git, Push and pull-request history evidence, Issue effects, forks and completed replay keep their records, identities, provider call counts and native-launch refusal unchanged; directory ensure adds only its own `workspace_file` event and resulting saved root | | ORC21 | Compiled binary | A compiled smoke creates a root-level ambient Worktree, runs a command there, proves `.git` is a file and the checkout persists after exit; a second gated process proves lock refusal and release | @@ -14215,4 +14215,4 @@ must preserve the trace for diagnosis or remove it before starting a new run. | 100 | `` → `` → `` is the explicit JSON direction | A document names a value, renders it as text, and validates text back into a value at three boundaries a reader can see. `{binding}` interpolation keeps its ordinary string coercion — there is no hidden JSON conversion — and `` takes no `indent`, `pretty`, replacer, sorting, canonicalization or newline option, so nothing about the format has to be agreed on per invocation. A file that must end in a newline authors that newline at the point it is written, rather than buying an option every other caller then has to reason about | | 101 | A capture is delivered from the authored expression | The scanner resolves a `{…}` prop whose text reads as JSON, but it runs before a name resolves and so cannot know the prop is a capture. Keeping the authored text beside the reading lets expansion decide with the selected definition in hand: a captured prop gets what its expression produced, every other prop gets the reading it always had, and an overriding repository file is an ordinary component either way. Without it `value={undefined}` reached a capturing component as `null`, which is the projection a capture exists to avoid | | 102 | A successful `undefined` omits an ordinary prop | Absence is the fact an author has to be able to state: an optional identity that a preceding result will supply is not there yet, and there is no value that means "not there" — `null` is a value, and a second element written for the other case duplicates the invocation. The omission is decided in the one resolver both component kinds pass through, before validation, so the schema answers it: optional stays unset, a `default` applies, a required prop fails as missing. It stops at the root and at that boundary — nested members keep native `JSON.stringify` normalization, failures stay failures, and captures and `` keep binding the exact value — and it puts `undefined` nowhere durable, because a prop that was never there is nothing to record | -| 103 | Protected `` and an authored loop, not hidden `` behavior | A workflow Agent's request/result exchange is written in the document: `` owns the bound, `` and `` own the branch, and `` is where a generated fragment is admitted and performed. `` stays exactly one durable turn, with no hidden retry, schema repair or observation loop inside it, and the Agent Api gains no second operation. A component rather than a new execution primitive, because the operation needs a name a trusted document can write and a schema a reader can check. Canonical core supplies [protected Evaluate](#532-evaluate-the-protected-evaluation-component); the host offers its generated selection, read and resolved before public execution policy. Protection fixes the implementation and grants no permission; every ceiling comes from that captured selection and the declarations that own the implementations it named. Exhaustion is therefore the document's own failure, stated where the bound is, with the turns that produced it retained | +| 103 | Protected `` and an authored loop, not hidden `` behavior | A workflow Agent's request/result exchange is written in the document: `` owns the bound, `` and `` own the branch, and `` is where a generated fragment is admitted and performed. `` stays exactly one durable turn, with no hidden retry, schema repair or observation loop inside it, and the Agent Api gains no second operation. A component rather than a new execution primitive, because the operation needs a name a trusted document can write and a schema a reader can check. Canonical core supplies [protected Evaluate](#532-evaluate-the-protected-evaluation-component); the host offers its generated selection, read and resolved before public execution policy. Protection fixes the implementation and grants no permission; every ceiling comes from that captured selection and the declarations that own the implementations it named. Exhaustion is therefore the document's own failure, stated where the bound is, with the turns that produced it saved | diff --git a/specs/native-agent-session-launch-spec.md b/specs/native-agent-session-launch-spec.md index ac8e2b668..d81347322 100644 --- a/specs/native-agent-session-launch-spec.md +++ b/specs/native-agent-session-launch-spec.md @@ -226,13 +226,13 @@ in for an hour, so no launch path sends a discard. `installed`, `resumed` and refusal where a recreation would have been convenient; what it buys is that no launch destroys a conversation XMD does not own and cannot see. -An empty cached transcript is not proof that a retained or previously handed-off +An empty cached transcript is not proof that a saved or previously handed-off session has no conversation. Native UI turns are provider-owned and need not be mirrored into ACPX or XMD state. Relaunch therefore never silently keeps a stale layer, discards unobserved native history, substitutes a new provider session -for retained continuity, or performs a bootstrap turn. A materialization turn is +for saved continuity, or performs a bootstrap turn. A materialization turn is not a bootstrap turn: it runs only for a conversation this launch just created, -carries no authored content, and never runs against a retained one. +carries no authored content, and never runs against a saved one. The prepared text and filesystem permissions are different capabilities: @@ -262,7 +262,7 @@ configured refuses before the provider prepares anything. The configured use remains valid only while the provider-installation owner that issued it is live. The launch provider reads it through the coordinator delivered directly to that installation; a same-named Context, sibling run, -retained post-teardown value or another loaded Core copy cannot supply that +saved post-teardown value or another loaded Core copy cannot supply that owner. Provider teardown invalidates lookup before a later launch can prepare anything. Placement completion and its registration state do not participate in launch admission: `AgentLaunchRequest` remains the launch's one-use identity. @@ -398,7 +398,7 @@ coordinator and the route store. It resolves the launcher command through the host's real execution environment, canonicalizes the target, requires an executable regular file, hashes that target, and asks that same file its version. It is deliberately not a Context, contextual Api, Agent operation, -component or middleware value: executable validation decides which retained +component or middleware value: executable validation decides which saved history may be accepted, and a resolver document middleware could replace could point the observation at one binary while the run spawns another. A controlled test substitutes the whole observer through the same constructor seam. @@ -467,7 +467,7 @@ the number because a bare semver from another tool compares equal to one from this one. What is exact is the session, not the release. The canonical string a -preparation accepted becomes that session's retained executable binding, and +preparation accepted becomes that session's saved executable binding, and every later create, resume, attachment and incomplete replay reobserves the live build and compares it against that binding before a process, an ensure or a turn. A build that no longer matches refuses without creating a substitute @@ -530,7 +530,7 @@ that turn is accepted: materialization plan this launch owes. Once `prepared` is durable — and only then — the fixed materialization turn - is announced on the reserved terminal and executed. It is retained as an + is announced on the reserved terminal and executed. It is saved as an ordinary `agent_prompt` **first** and as `materialized` **second**, because the prompt says the turn was spent and the phase says the launch may go on. Only the backend's explicit acceptance of that turn promotes the provider @@ -552,7 +552,7 @@ owner to release — what has to be settled first is which conversation this is: `executable-binding-refused`. A legacy unbound route is the exception: it observes nothing, resumes under the launcher name, and gains no binding. Whether an identity is needed at all is decided next. An existing compatible - `client-native` route already names this conversation, so its retained + `client-native` route already names this conversation, so its saved identity is adopted and **nothing is allocated** — a second candidate for a conversation that already exists is a value with nowhere to go. Only where no route names it yet does the adapter allocate one, inside ownership and @@ -574,7 +574,7 @@ owner to release — what has to be settled first is which conversation this is: construction rather than by comparison. A refusal is not: it prepared no identity, so it retains the failure the authoritative winner produced without mirroring that route's identity or provenance — no session id, and - the weaker provenance claim, because nobody chose one. It is retained at + the weaker provenance claim, because nobody chose one. It is saved at `prepared` and reaches no private file, no detach and no spawn. 12. The prepared instructions are written to a private mode-`0600` file. Detach still happens as a phase — it is the point after which a spawn may happen — @@ -594,7 +594,7 @@ From there both rejoin: transcript persistence directly. 16. When the child exits, XMD records its terminal outcome, removes the private file and its directory while ownership is still held, and releases the - foreground-terminal lease. The route and the retained phases stay: they are + foreground-terminal lease. The route and the saved phases stay: they are what the next invocation resumes from. 17. Later Agent work depends on the route again. On an `acp-first` session it lazily reattaches through ACP to the same provider session. On a bound @@ -628,7 +628,7 @@ this sequence, and every step happens while the coordinator holds the session: `executable-binding-refused`, and an agent this host has not advertised for attachment refuses with `unsupported-capability`. 5. Reobserve the executable and compare the binding exactly. -6. Inspect any retained ACP arrangement without creating one: absence may enter +6. Inspect any saved ACP arrangement without creating one: absence may enter exact resume, because exact resume is the operation being attempted; a record must assert this route's identity and nothing else. 7. Select the live runtime for `(resolved agent command, binding)` and give the @@ -942,7 +942,7 @@ no longer execute or hold the foreground terminal, restores that terminal's state, and runs every provider finalizer. A process that ignores the initial interruption is terminated according to the host process adapter's bounded shutdown policy. -The adapter attempts to collect the native exit status, but a runtime-retained +The adapter attempts to collect the native exit status, but a runtime-saved defunct PID or a lost exit event is not live process ownership. After a fatal signal was accepted, or the process was already absent, bounded settlement may release the runtime handle without waiting for signal-zero reachability to @@ -962,7 +962,7 @@ and lifecycle phases rather than the native conversation: prepared -> [materialization agent_prompt -> materialized]? -> detached -> launched -> exited ``` -`launched` is live-only and is never retained. The bracketed pair appears only +`launched` is live-only and is never saved. The bracketed pair appears only where the adapter declared a materialization operation *and* this launch created the conversation; everywhere else neither the prompt nor the phase exists. They are two durable operations rather than one because they answer different @@ -970,8 +970,8 @@ questions: the `agent_prompt` says the turn was spent, and `materialized` says the launch may go on. A run that stopped between them replays the first and rebuilds the second, which is why the prompt is appended first. -Each phase the launch completes is one retained record under that identity, -because the preparation has to be retained *before* anything of the session +Each phase the launch completes is one saved record under that identity, +because the preparation has to be saved *before* anything of the session moves — before ACP ownership is released where ACP holds it, and before a native process exists where the adapter named the session itself. A single record written when the whole launch settles would describe nothing at @@ -979,16 +979,16 @@ all for the run that was interrupted between detach and spawn, which is exactly the run that must not create a replacement session. `launched` is the live state between the spawn and the child's exit, and is -deliberately not retained: an interrupted native process leaves `detached` as -the last retained phase, and resuming reattaches the native UI to that same +deliberately not saved: an interrupted native process leaves `detached` as +the last saved phase, and resuming reattaches the native UI to that same provider session. -For a session XMD named, the retained phase is what decides the only safe -continuation, because `detached` is retained before the exit phase is invoked: +For a session XMD named, the saved phase is what decides the only safe +continuation, because `detached` is saved before the exit phase is invoked: - **`prepared` only** — the handoff never began, so native creation may still be - owed. The replay creates under the same retained identity, from a new private - file holding the exact retained instructions. It allocates nothing and + owed. The replay creates under the same saved identity, from a new private + file holding the exact saved instructions. It allocates nothing and publishes no route. - **`detached`** — a process may already have started under this identity, so the replay resumes and never falls back to creating. @@ -1017,19 +1017,19 @@ An **already-established** provider-returned session takes none of that path. Its identity is known, so `prepared` retains it, carries no materialization plan, and the launch spends no turn. -- **`prepared` with an owed plan and no retained prompt outcome** — ambiguous. - The preparation is retained immediately before the turn is sent, so this is a +- **`prepared` with an owed plan and no saved prompt outcome** — ambiguous. + The preparation is saved immediately before the turn is sent, so this is a run that reached the sending point and never recorded what happened there. Nothing reachable from the journal separates a turn that was never sent from one the backend accepted and the interrupted run never saw, and sending now, if it is the second, spends a turn in someone's conversation to find out. The replay stops and retains `session-recovery-required`. **It never sends another turn**, and every later replay says the same thing without reconsidering it. -- **a retained materialization `agent_prompt` without `materialized`** — the +- **a saved materialization `agent_prompt` without `materialized`** — the turn is already spent and its outcome is on the journal. The replay reconstructs the phase from that record, with no provider work and no second turn. -- **a retained `materialized`, successful or failed** — replays as itself. A +- **a saved `materialized`, successful or failed** — replays as itself. A failed one stays failed; it is not retried. - **`detached` or later** — a process may already have started, so the replay resumes and never falls back to creating. @@ -1039,7 +1039,7 @@ plan, and the launch spends no turn. The materialization prompt and the authored instructions are separate throughout. The prompt is the adapter's own fixed bytes, interpolating nothing and carrying no authored content; the rendered `` body is -retained with the preparation and is what governs the **first native user +saved with the preparation and is what governs the **first native user turn**. Neither is ever substituted for the other, and the turn spent here cannot perform the task the body describes. @@ -1058,7 +1058,7 @@ before its first live effect, and then requires the live build to equal that binding. Neither account repairs or republishes the other: a replay that found a disagreement has discovered that the session it was going to continue is not the session it prepared, and retains `identity-unavailable` without starting -a child. Equal instructions may resume the retained identity; different +a child. Equal instructions may resume the saved identity; different instructions retain `instructions-refused` and replace neither the layer, the route, the identity, nor any provider state. @@ -1074,7 +1074,7 @@ file, mode `0600`, passed by path. The text appears in neither argv nor environment, and the file and its directory are removed on success, ordinary failure and cancellation alike, while coordinator ownership is still held. Every private setup or child-creation failure is normalized before it crosses a -public or durable boundary: the retained class is `process-creation-failed` and +public or durable boundary: the saved class is `process-creation-failed` and the message is fixed provider-owned text carrying no executable path, instruction-file path, argv, environment, raw host message, credential or provider-private state. @@ -1086,12 +1086,12 @@ before the next live effect, and cross-checks the preparation against the exact request that was routed — instructions and digest, agent, requested session, cwd, the V1 empty additional-directory list, and permission mode. A record that changed what was asked would make the journal describe a launch nobody -authored, so it is refused rather than retained. +authored, so it is refused rather than saved. That is the coordination boundary. Middleware composed around the Agent Api may observe a launch or refuse one; it cannot author a phase, and a completion it returns settles nothing, because the route ignores returns and the result is -derived from the retained records alone. +derived from the saved records alone. At minimum the records retain: @@ -1124,13 +1124,13 @@ and unfiltered CLI arguments do not enter the record. A completed replay restores the terminal outcome and contacts no provider, coordinator, terminal, or process. A partial replay reserves the terminal, -acquires session ownership, verifies the retained request against the live one, +acquires session ownership, verifies the saved request against the live one, reuses the recorded native session, and continues at the first incomplete phase. A partial replay whose native process was interrupted reattaches the native UI to that same session; it never creates a replacement or reconstructs state from a transcript. Those are operation/runtime replay semantics: they define how an execution -behaves when an embedder, a test, or a future retained execution host supplies +behaves when an embedder, a test, or a future saved execution host supplies the launch's durable history again. They do not create a public continuation command. Diagnostic persistence remains `xmd run --journal`, and the ordinary run command exposes no resume UX; only `xmd workflow resume` is a current public @@ -1152,7 +1152,7 @@ Failure to configure instructions or the contextual cwd leaves ownership with ACP and does not spawn the native UI. Missing native-launch capability fails before detach. Detach failure also prevents spawn. -Changing instructions on a retained session fails with `instructions-refused` +Changing instructions on a saved session fails with `instructions-refused` whenever the provider cannot replace the layer in place while preserving identity and history. Nothing is discarded to make room for a layer, and an empty cached transcript authorizes nothing: a session an enclosing `` @@ -1171,17 +1171,17 @@ environment, credential, instruction text or provider payload. An attachment that reaches the provider and cannot open the conversation the route names fails with `identity-unavailable`: missing provider history, an -adapter that cannot resume by name, a retained provider arrangement asserting +adapter that cannot resume by name, a saved provider arrangement asserting another conversation or none, and a returned identity that differs from the route's are one answer, and none of them creates a substitute conversation. A launch that cannot take ownership retains `session-busy` or `session-recovery-required` as its preparation and stops there. Both are -retained rather than raised bare, so a replay resumes from the phase that +saved rather than raised bare, so a replay resumes from the phase that actually happened, and both refuse before ACP ensure, detach, or spawn. If detach succeeds but process creation fails, the durable provider session -remains the authoritative retained state. The launch fails and a later replay +remains the authoritative saved state. The launch fails and a later replay uses that same identity. If the native UI mutates its session and then exits unsuccessfully, both facts @@ -1284,7 +1284,7 @@ Focused tests prove: 20. a `` or `` on a bound route supplies the route identity as the exact resume identity, delivers the observed path only to the matching child's transient environment, and refuses before ensure on a missing - attachment gate, a missing observer, build drift, a disagreeing retained + attachment gate, a missing observer, build drift, a disagreeing saved arrangement or a returned identity that is not the route's; and 21. ACP runtimes are partitioned by resolved agent command and binding, a handle is closed by the partition that created it, the last close evicts a bound @@ -1351,7 +1351,7 @@ history marker or a private path. A proof also removes what it created, and that is the harness's own act rather than anything the product does. Production keeps the construction route and the -retained phases — a launch that deleted them would break the continuity claim 4 +saved phases — a launch that deleted them would break the continuity claim 4 asserts. A proof reads those exact records for its own natural key, reduces them to filtered evidence, and only then removes those exact paths, together with its temporary project through the provider's own path-scoped purge. It never sweeps @@ -1369,11 +1369,11 @@ records with their replay and secret scanning; the host-owned session coordinator and its crash-conservative ownership record; an invocation-owned native-launch capability in the ACPX provider; provider-native identity that is either asserted by the provider or allocated by -the adapter before the provider exists, retained explicitly and never inferred; +the adapter before the provider exists, saved explicitly and never inferred; a strict create-once construction route beside the coordinator's own records, in a released unbound form and a bound one; the host-owned executable observer and the build binding it produces; ACP attachment to a bound client-native -session under its exact retained identity, through runtime partitions keyed by +session under its exact saved identity, through runtime partitions keyed by agent command and build; an inherited foreground-terminal interactive child with cancellation and bounded reaping; @@ -1401,7 +1401,7 @@ degrading: with `unsupported-capability` before anything of the session moves, and so is an attachment naming an agent advertised only for native launch. - **`Agent.AddDir` is unbuilt**, so a launch declares no additional roots. The - retained request says so explicitly — an empty ordered list — rather than + saved request says so explicitly — an empty ordered list — rather than omitting the fact, and no adapter maps a root it was never given. The ACP Client specification is the prerequisite owner for a future ordering and access-mode contract. @@ -1425,10 +1425,10 @@ degrading: spawned. An unconfigured launch on either route is unchanged and reads no configuration at all. Middleware that routes a raw Session or no Session has selected that unchanged unconfigured path. -- **An incomplete configured launch reapplies what it retained.** At its own +- **An incomplete configured launch reapplies what it saved.** At its own first live phase — the detach for a launch that never handed over, the spawn for one that did — it reconciles its route and build, reattaches by the exact - retained identity, puts the retained configuration back into force, and + saved identity, puts the saved configuration back into force, and relinquishes ACP again without authoring a second detach. - **Released launch model members never become configuration.** A non-empty `requestedModel` or top-level observational `model` string is validated and @@ -1469,7 +1469,7 @@ Implementation review checks these frozen invariants: 6. Cancellation establishes that the native child can no longer execute or own the terminal before document continuation or teardown completion; a lost runtime exit event or defunct PID does not extend live ownership. -7. Completed replay launches nothing; incomplete replay uses the same retained +7. Completed replay launches nothing; incomplete replay uses the same saved provider session. 8. Native conversation history remains provider-owned and is never reconstructed from XMD output. @@ -1497,7 +1497,7 @@ Implementation review checks these frozen invariants: every later create, resume, attachment and incomplete replay reobserves and compares before a process, an ensure or a turn. 19. The canonical executable path is live only: it enters no route, journal, - retained provider state, public result, diagnostic or global environment, + saved provider state, public result, diagnostic or global environment, and no partition that outlives its last handle. 20. Attachment supplies the route's exact identity as the resume identity, and the provider's canonical assertion must equal it before the first turn. diff --git a/specs/plan-command-spec.md b/specs/plan-command-spec.md index 16ecd84e5..20090cf4d 100644 --- a/specs/plan-command-spec.md +++ b/specs/plan-command-spec.md @@ -214,7 +214,7 @@ token, coerce its value, or read it as a second positional. An option's name is read up to its first `=`, so every valued spelling arrives under the name it was written as; a removed option consumes no following token and inspects no candidate schema. It is refused wherever it stands — before the request, after -it, beside a retained option, and in the place a retained option's value would +it, beside a saved option, and in the place a saved option's value would otherwise have swallowed it. **Including beside `--help`.** `--help` is lifted out of the command line before @@ -767,7 +767,7 @@ reaches neither verbose output nor a following Agent Prompt. Secret or scanner failure is terminal. *Continuation is ordinary.* [Generated-work ownership](../architecture.md#agent-permissions-and-generated-xmd) -keeps each occurrence's retained work separate. A completed request replays +keeps each occurrence's saved work separate. A completed request replays without another Agent turn, Syntax lookup, File read or Glob traversal; a partial continuation restores completed effects and resumes at the first unrecorded one. Requests add @@ -895,7 +895,7 @@ about command output. Neither surface starts a ninth evaluation or another turn. **Stale continuation:** -> Plan authorship cannot resume because retained XMD information does not match +> Plan authorship cannot resume because saved XMD information does not match > the current request or evaluation policy. Start a new Plan authorship instead > of continuing this one. @@ -1224,10 +1224,10 @@ neither observation never interpreted what it wrote. | # | Criterion | Required observation | | --- | --- | --- | -| PS1 | Fixed grammar | Every retained option is accepted before and after the request, one request is preserved byte for byte, and a second positional is refused with the approved sentence | +| PS1 | Fixed grammar | Every saved option is accepted before and after the request, one request is preserved byte for byte, and a second positional is refused with the approved sentence | | PS2 | The removed switch | Bare, valued, repeated, before-request, after-request and value-position `--run` forms all return the exact migration text, before any symbols, Agent, session, review, filesystem or document activity | | PS3 | The removed options | One representative of every other removed class, both short aliases, and the aggregate and generated property names return the exact generic refusal before authorship, ahead of the shared timeout and secret-detection grammar checks; a name that merely begins like a property option keeps the generic unknown-option refusal | -| PS4 | Help | The complete `xmd plan --help` output and the program summary contain only the retained grammar and both explicit compositions; no removed option appears anywhere in either. Help beside every retained option is still help; help beside a removed one, in either order, is that option's refusal | +| PS4 | Help | The complete `xmd plan --help` output and the program summary contain only the saved grammar and both explicit compositions; no removed option appears anywhere in either. Help beside every saved option is still help; help beside a removed one, in either order, is that option's refusal | | PS5 | Run is unchanged | `xmd run --help` still exposes its execution, prop, permission, timeout, presentation, journal and secret-detection options | | PS6 | Stdout | Approval without `--output` writes the exact source once, with no wrapper and no added newline, and a source-named effect and runtime failure both stay inert | | PS7 | The artifact | Approval with `--output` writes the same exact bytes, keeps stdout empty, creates the path only after authorship teardown and structural validation, and refuses an existing path without changing it | @@ -1243,7 +1243,7 @@ neither observation never interpreted what it wrote. | PO4 | The ordinary surface is silent | A bare `` emits only exact approved source, a captured `` binds the same bytes and emits nothing, and neither expands a progress body whatever verbosity the declaration carries | | PO5 | Disclosure | Default progress excludes the request, the drafts, the diagnostics, the feedback and every Agent, provider and tool output; verbose adds every cleared draft and each invalid check's exact structured JSON, in phase order, and nothing else | | PO6 | Channels | A non-terminal stderr receives normalized Markdown, a stated terminal receives it rendered, and stdout and `--output` stay byte-identical exact source in both | -| PO7 | The two options | `--verbose` and `--journal` are accepted on either side of the request, help contains them and the journal warning, and `-V`, `-j`, `--trace` and every removed spelling refuse before the symbols or a session exists; a retained option that reaches this command's grammar, written where the journal path goes, is that option rather than a filename and refuses before any symbols, session, provider, filesystem or artifact work — while `--help` and `-h` keep their ordinary precedence and answer with help, creating no journal and beginning no authorship | +| PO7 | The two options | `--verbose` and `--journal` are accepted on either side of the request, help contains them and the journal warning, and `-V`, `-j`, `--trace` and every removed spelling refuse before the symbols or a session exists; a saved option that reaches this command's grammar, written where the journal path goes, is that option rather than a filename and refuses before any symbols, session, provider, filesystem or artifact work — while `--help` and `-h` keep their ordinary precedence and answer with help, creating no journal and beginning no authorship | | PO8 | The journal file | With no `--journal` no file appears; with one, the path exists before the symbols and the first turn, a successful trace parses as the existing JSONL events in commit order and ends terminally, and it holds no program-execution event | | PO9 | Journal refusals | A pre-existing journal is byte-identical and refuses with the exact copy before any symbols, session, turn, review or artifact work; a path that cannot be created reports the other exact copy | | PO10 | A secret in a draft | It reaches neither the progress nor the journal, the earlier prefix stays readable, teardown completes, and no source or artifact is delivered — while the same draft without it is displayed and recorded | diff --git a/specs/repl-spec.md b/specs/repl-spec.md index 869dff948..86a685f4a 100644 --- a/specs/repl-spec.md +++ b/specs/repl-spec.md @@ -7,7 +7,7 @@ another one. ```bash xmd repl # a fresh execution with an empty draft -xmd repl 'xmd://repl//entries' # reopen exactly that retained history +xmd repl 'xmd://repl//entries' # reopen exactly that saved history xmd repl --deny-all # the same, answering every agent request with no ``` @@ -155,12 +155,12 @@ reading an earlier position is refused, and refusing it starts nothing. ## What a later entry starts from The root values an earlier entry durably published are what the next one begins -with. For each name it is the last value any earlier entry retained, in Journal +with. For each name it is the last value any earlier entry saved, in Journal order, and the values are the entry's own to change — they behave like values authored earlier in the same environment, and a later export replaces one through the ordinary rules. -What survives is what was retained. An entry that failed, and one whose +What survives is what was saved. An entry that failed, and one whose cancellation the file records, each settle their entry: they hand on everything they published before that, hand on nothing they never durably published, and let the next entry start. The reserved `props` namespace belongs to each entry's @@ -186,7 +186,7 @@ answer, the same History positions and the same terminal output. Nothing is re-run to do it. No component source is read, no `eval` block is compiled, and nobody is asked anything: everything on that screen was -reconstructed from the location and the retained events. Settled entries are +reconstructed from the location and the saved events. Settled entries are projected and never performed again; only a final entry the file shows as unfinished is given back to the engine to continue. A location naming a history this version cannot read, or naming an entry its selected prefix never admitted, @@ -254,7 +254,7 @@ frame can place, with its earlier and later controls — and the way to the othe surface — staying put while the rows move beneath them. What the window is not showing is not drawn, not focusable and reaches no pointer. -## Permission: a live request, and a retained audit +## Permission: a live request, and a saved audit A turn waiting for permission says so on its own row, and that is all arriving does: nothing moves, nothing takes focus, and nothing else stops. Activate it and @@ -271,7 +271,7 @@ way the drawer closes only when the request is settled — a screen that closed first would be claiming an answer nobody gave — and focus returns to the turn that was waiting. -A retained audit is inert. What the history holds is what was granted, read and +A saved audit is inert. What the history holds is what was granted, read and never answered again — a recorded request offers no control, and a cold process shows the audit without offering to decide anything. Ending the command answers nothing: a torn-down process does not fabricate a denial, a selection or a @@ -282,7 +282,7 @@ cancelled audit on the person's behalf. A drawer shows as much of what it holds as the frame has room for, and its earlier and later controls reach the rest. That is true of all five readings a drawer can show: the question being asked, a pending permission request, a -History position, a binding's retained value and a recorded answer. A retained +History position, a binding's saved value and a recorded answer. A saved value is as long as whatever produced it — a compiler's reason, a whole JSON document — so a drawer that described all of it at once would have its tail placed nowhere, which is the one part you opened the drawer to read. @@ -333,7 +333,7 @@ deliberately cleared is present and empty; one nobody touched is absent. ## Cold Agent reconstruction -A cold process over a retained location shows the Agent work the history holds: +A cold process over a saved location shows the Agent work the history holds: the turns, their conversations, their text, how each ended, the questions that were asked and the answers that were given, the program a generated fragment admitted and what it wrote. @@ -345,7 +345,7 @@ one the history already describes — and the program that Component returned is scope of its own beneath it, owning the questions the program asked, which carry a place in the generated source and no path at all. A turn whose provider failed or was cancelled restores with the text it had streamed and the status it ended with, as terminal -retained state — never as queued, streaming or reconnecting. +saved state — never as queued, streaming or reconnecting. No provider is reached to do it, no adapter is materialized, and nothing is appended: the history file is byte-identical before and after a cold run. @@ -452,7 +452,7 @@ and a kind per position, and nothing of what any of them holds. The map and the content are one reading, taken from one acknowledged array and carried together through the frame. Nothing on this screen pairs a prefix with -a rail the file no longer has: a record retained while the terminal is being +a rail the file no longer has: a record saved while the terminal is being measured changes the next reading, not half of this one. Where that reading cannot be built at all, because the file holds a record this process can no longer read, the whole screen is the refusal — not a rail standing beside a @@ -503,7 +503,7 @@ a prefix of the row you read at a larger one and nothing moves under your hand. Every row says what it has room to say. A row is as wide as the region the frame measured for it, and so is its text: a name nobody bounded — a prompt, a root, a tool call — is shortened with an ellipsis so that what the row is *about* stays -on it. A turn keeps its state, a retained grant keeps its outcome, an entry keeps +on it. A turn keeps its state, a saved grant keeps its outcome, an entry keeps its outcome, and where a row is too narrow even for that, the fact survives and the name keeps whatever is left in front of it. What the row shows is shortened; what the product holds is not, and the whole of it is still in the record, the @@ -550,13 +550,13 @@ renderer cuts loses its last fact with nothing to say that it is missing. side cannot answer it alone: a root close is recorded the moment a document settles, while the task that produced it is still coming down, and a submission taken in that window has nowhere to go. So there is a state between "settled" and -"ready" — the close is retained, the entry's task has not been joined, and no +"ready" — the close is saved, the entry's task has not been joined, and no successor may start yet. It resolves without anybody typing: when the teardown finishes, the row says the next entry may start. The states a person can tell apart: no entry yet; an entry running; an entry waiting for an answer; an entry waiting for a permission; an entry whose close is -retained while its teardown finishes; an entry admitted that never reached an +saved while its teardown finishes; an entry admitted that never reached an outcome, which no successor may follow; and a settled execution ready for the next entry. A frozen position is its own state and says so before any of them. @@ -588,7 +588,7 @@ running, not what is waiting, not what could be paused — because a prefix is a reading of the file and the head is not part of it. The draft belongs to no position and is unchanged. -None of this is retained. The status, the guidance and the emphasis are computed +None of this is saved. The status, the guidance and the emphasis are computed on the way to a frame from the resolved view; they are in no location member and no record, and replay neither produces nor consumes them. A cold reopen shows the same state because the file and the location say the same things, not because @@ -600,7 +600,7 @@ Every row of this screen is drawn as the thing it is. What a run produced stands ahead of the facts about the events that produced it; an entry that closed `ok`, one that closed `err` and one still running are three different readings rather than three spellings of the same one; a question nobody has answered yet reads as -waiting; and a retained position reads as history until you return to the head. +waiting; and a saved position reads as history until you return to the head. A drawer says which kind of reading it is showing before it says which one — a binding, a recorded answer, a permission request — and separates what a field is called from what it means from the value you are editing. Whatever acts carries @@ -663,7 +663,7 @@ questions at once are two waiting elements rather than one screen that is busy. All of it is this process's, and none of it is written down. A cold process reconstructs none of it, a reading frozen at an earlier position shows none of it, and an entry that finishes takes its own with it. Source that is still on -the screen and a record the history retained prove only that something was +the screen and a record the history saved prove only that something was written and something was recorded — never that anything is running now. ## One entry, read as output and then source @@ -680,10 +680,10 @@ A reading belongs to an entry; with nothing selected it is the last entry the prefix admitted, which is what a one-entry execution has always shown. Output says which of four things is true, and never two of them. Text still -arriving is `Output · live`. The retained final result replaces it once and the +arriving is `Output · live`. The saved final result replaces it once and the label becomes `Output`. A root that settled having rendered nothing reads `No rendered output.` and then the outcome it closed with, and a recorded reason -beside it. A prefix that retained no output for its entry reads `No entry +beside it. A prefix that saved no output for its entry reads `No entry output recorded at this checkpoint.` — never text borrowed from a later position or from a run still going. @@ -709,7 +709,7 @@ twice, makes several *calls* of it at one position. Each is its own reading, in its own group, in the order they were observed: showing only the last would say the earlier ones never happened, and one badge for all of them would say several calls were one. An element nothing was observed of is read once, unobserved. Source nothing has been observed in gets the rail that means -exactly that, and acquires no phase from retained syntax. +exactly that, and acquires no phase from saved syntax. An `` that admitted a generated fragment shows that fragment where its producer was written. The enclosure bytes stay exactly as authored and only what @@ -749,7 +749,7 @@ A region with no room for even one grapheme is the frame refusal that already recovers on resize. It is never a clip: shortening somebody's source would be this screen editing the content it exists to show. -## What is retained, and what is not +## What is saved, and what is not Each execution is one file of serialized ordinary `DurableEvent`s — the same records any other XMD run writes. There is no manifest, no cache, no materialized @@ -767,7 +767,7 @@ internals, rendered cells or this process's overlay. ### The files an entry wrote in the checkout you are standing in -Start the REPL inside a Git checkout and an entry's file work is retained too, +Start the REPL inside a Git checkout and an entry's file work is saved too, under the ordinary records above and no record type of its own. Four things are kept: a self-closing `` text read; a paired `…` write, after its content has expanded; a self-closing @@ -791,18 +791,18 @@ be recorded, and a continuation then does that one operation again. Work that *was* recorded before you left is answered from the record, so an edit somebody made in the meantime survives the continuation untouched. -A retained read is the same rule, which is worth saying plainly because its +A saved read is the same rule, which is worth saying plainly because its answer is a value you can see. The binding a completed read produced is the text it read when it ran: a question showing that binding still shows it after the file has changed, because nothing re-reads the file to answer an operation that is already done. Type a *new* entry that reads the same path and it reads what -is there now. What is retained is each operation, not a value the session +is there now. What is saved is each operation, not a value the session remembers for a path. Any of the four anywhere else — beside the document, in a temporary directory, in any directory that is not inside the checkout the entry started in — is -ordinary file work, is retained by nothing here, and happens again if the entry -is continued. That includes a read: what is retained is a read *inside* the +ordinary file work, is saved by nothing here, and happens again if the entry +is continued. That includes a read: what is saved is a read *inside* the checkout. Searches and temporary directories are ordinary throughout, wherever they point. The lexical path rules and the permission a `` already answers to are unchanged, and they are not checkout validation: a path this run may not @@ -828,16 +828,16 @@ looks for a repository twice. ## What the head says the execution is doing -Seven states and no eighth, in one precedence, each a fact about retained +Seven states and no eighth, in one precedence, each a fact about saved records and about work this process actually owns: | | Reading | When | | --- | --- | --- | -| 1 | `HEAD · SETTLING` | an outcome is retained and the entry's work is still coming down | +| 1 | `HEAD · SETTLING` | an outcome is saved and the entry's work is still coming down | | 2 | `HEAD · EXPANSION PAUSED` | no outcome, and the controller is paused | | 3 | `HEAD · PAUSING EXPANSION` | no outcome, and it is pausing | | 4 | `HEAD · LIVE` | work this process owns, and none of the above | -| 5 | `HEAD · EMPTY` | nothing retained and nothing owned | +| 5 | `HEAD · EMPTY` | nothing saved and nothing owned | | 6 | `HEAD · SETTLED` | no owned work, and the latest entry has an outcome | | 7 | `HEAD · UNFINISHED` | no owned work, and it has none | @@ -845,7 +845,7 @@ Owned work means every resource the entry acquired is still standing — its outer Agent attachment and its consumers included. A body that returned and a root Close that was recorded are both earlier than that, so neither of them says an entry has finished; `SETTLING` is the window between them, and it is -the one a reader watching a run end is actually in. A retained success, a +the one a reader watching a run end is actually in. A saved success, a failure and a cancellation all become a neutral `SETTLED`: which of them it was belongs to the reading, not to the head. An entry with no outcome and nothing holding it is `UNFINISHED` — stopped, never an inferred pause. @@ -882,7 +882,7 @@ text. No second name for the entry appears in a record: which history an entry may continue is the range it was given. Reading is not owning: a settled history, a frozen position and a refused location prepare nothing, look for no checkout and hold nothing. A host that registers no recording runs the same -file work with nothing retained, which is what Node and Bun do. +file work with nothing saved, which is what Node and Bun do. Components receive immutable view data and answer with a typed semantic action. They hold no events, no history, no session, no repository and no host operation, @@ -934,7 +934,7 @@ schedules no timer. - The REPL runs where a terminal is: it is not available over a pipe, and help acquires no terminal or filesystem capability. Whether there is a terminal is settled before a history exists, so a piped invocation leaves nothing behind. -- A location is resolved against the retained history before anything replays. A +- A location is resolved against the saved history before anything replays. A route naming a scope or a drawer the file does not hold is refused without the execution being started, so a typo cannot cause a record. - A waiting question's drawer cannot be reopened from a URL. A Journal records diff --git a/specs/root-document-props-spec.md b/specs/root-document-props-spec.md index 3b3960eea..43ceb943b 100644 --- a/specs/root-document-props-spec.md +++ b/specs/root-document-props-spec.md @@ -402,7 +402,7 @@ a raw `%` begins escape syntax wherever it appears, so `pct%zz.md` is refused as a malformed reference and written `pct%25zz.md` instead. Props are unaffected by the selection, because they are the document's. The -frontmatter that declares them is retained by every projection, so the same +frontmatter that declares them is saved by every projection, so the same options, environment variables, defaults, requirements, and `--help` section apply — the declaring document is named by its path, and interpolation resolves against the projected body. A required property is still required when only one diff --git a/specs/upgrade-command-spec.md b/specs/upgrade-command-spec.md index 6086a2b43..8efa050fe 100644 --- a/specs/upgrade-command-spec.md +++ b/specs/upgrade-command-spec.md @@ -490,7 +490,7 @@ protects can exist: would have teardown delete a stranger's file. Filesystem work goes through `@effectionx/fs` — `lstat` and `rm` here — and -`until` is retained only for the asynchronous primitives that package does not +`until` is saved only for the asynchronous primitives that package does not provide: `open`, `rename`, `chmod` and the writable-mode `access`. ## Private state diff --git a/specs/workflow-spec.md b/specs/workflow-spec.md index b38de942f..c4c6987bf 100644 --- a/specs/workflow-spec.md +++ b/specs/workflow-spec.md @@ -17,14 +17,14 @@ outlives any one of them. A run has one definition: the exact source bytes chosen when it starts. A **source-bundle** definition is the exact bytes themselves, addressed by -portable logical paths and retained with the run. A file outside a repository, +portable logical paths and saved with the run. A file outside a repository, an untracked file and a file edited since its last commit are all ordinary starts, and the run executes what each said at the moment it began. Editing, moving or deleting the original afterwards changes nothing about the run. The definition has one closed shape, with no `version` or `kind` member (§9.1). Older development shapes are refused; no migration or legacy reader -exists. Creation, resume, fork and export read the run's retained source rather +exists. Creation, resume, fork and export read the run's saved source rather than asking a host to fetch its definition. For the execution and replay model, read [A document running](../architecture.md#a-document-running). @@ -33,15 +33,15 @@ owns lifecycle commands and the authored workflow environment. ```ts import { executeInstalled } from "@executablemd/core/host"; -import { getWorkflowRun, retainedWorkflowInstallation } from "@executablemd/workflow"; +import { getWorkflowRun, savedWorkflowInstallation } from "@executablemd/workflow"; const execution = yield* executeInstalled( { path: "./workflow.md", stream }, - [retainedWorkflowInstallation(run)], + [savedWorkflowInstallation(run)], ); ``` -The package owns `WorkflowRun`, `retainedWorkflowInstallation()`, +The package owns `WorkflowRun`, `savedWorkflowInstallation()`, `getWorkflowRun()` and the source-bundle identity primitives. It depends on `@executablemd/core`, `@executablemd/durable-streams` and `@executablemd/runtime`. Core never imports workflow, and workflow never imports @@ -75,19 +75,19 @@ interface WorkflowRun { random IDs by default; an authorized local caller may [select an ID](workflow-workspace-spec.md#33-callers-may-select-a-run-id). A programmatic host supplies its ID through its own preparation (§3). `bundleHash` is the identity of the bytes the run retains, and `targetPath` is the exact section it runs, when it runs one. There is no base -and no pinned commit: a run starts from exact retained bytes rather than from a +and no pinned commit: a run starts from exact saved bytes rather than from a repository state, so a member naming one would be a claim about a repository the run never had. The shape is closed, and reading it is exact. A value carrying that member set in any key order is a run; a value carrying it with something extra — including -the members a record retained under an older format carried — is not a run read +the members a record saved under an older format carried — is not a run read loosely and is refused. Ordinary serialization writes `runId`, `bundleHash`, then `targetPath` when present. Object-member order is presentation: a canonical-JSON container sorts these keys under its own rule without changing the value. -The retained effect description records +The saved effect description records `{ type: "workflow_run", name: "workflow_run", bundleHash }` and invents no Git field. Divergence detection compares only the type and the name, so the members past them are for a reader. @@ -105,7 +105,7 @@ workspace or continuation capability. ## 3. Where the run is prepared A trusted host supplies one of two preparations. For a run already created -by the lifecycle, `retainedWorkflowInstallation(run)` records that exact run. +by the lifecycle, `savedWorkflowInstallation(run)` records that exact run. A programmatic host instead supplies `WorkflowRunPreparation.allocate()` to `createWorkflowRunInstallation(preparation)` and decides how to allocate its run and obtain its inputs. @@ -115,10 +115,10 @@ The host passes it to `executeInstalled()`, where it contributes two things: - **an admission**, which canonical core captures before any installation, middleware or document code exists and applies inside its own trusted journal - read, on the retained snapshot every later phase consumes; and + read, on the saved snapshot every later phase consumes; and - **a `prepare` hook** — a `DurablePreparation`, the trusted durable preparation canonical core invokes *inside the durable root*, after - retained-history admission and before any public `Execution.document` policy, + saved-history admission and before any public `Execution.document` policy, the root import and every authored effect. No public middleware settles workflow-run state. `Execution.execute`, @@ -138,14 +138,14 @@ executions and neither sees the other's run. ### 3.1 Installing a run that already exists -A host that keeps runs in retained storage (§9) has decided what the run is +A host that keeps runs in saved storage (§9) has decided what the run is before anything executes: the lifecycle transition answered with the run id and -with the definition it retained. There is nothing left for the execution to +with the definition it saved. There is nothing left for the execution to allocate or resolve, and a run id an execution invented could not agree with the record storage already holds. ```ts -yield* executeInstalled(options, [retainedWorkflowInstallation(run)]); +yield* executeInstalled(options, [savedWorkflowInstallation(run)]); ``` `run` is the closed `WorkflowRun` value the record retains. @@ -159,9 +159,9 @@ it. A journal recording a different run is refused as `StaleInputError`, naming the differing fields and never their values. Run IDs and targets can contain -external text, just as retained props can. A serialized journal value missing a +external text, just as saved props can. A serialized journal value missing a required member or carrying any extra member identifies no run and is refused. -The host input is a separate boundary: the retained installation projects the +The host input is a separate boundary: the saved installation projects the named run fields from a possibly wider record, then validates them. Missing, unreadable or empty required fields are refused before document execution. @@ -206,7 +206,7 @@ that stream's journal-provenance witness onto itself without establishing one. What a history is held to is the run the installation supplied. -`retainedWorkflowInstallation(run)` requires `runId`, `bundleHash` and the exact +`savedWorkflowInstallation(run)` requires `runId`, `bundleHash` and the exact target to match, and requires the record to be *there*: a host created the run before anything executed, so a non-empty history carrying no successful record — none at all, or only one that failed — is not this run's history. @@ -234,13 +234,13 @@ history may carry **at most one** entry under the canonical run identity — the record is written before the root document is imported, so a second entry describes a second run, *however either of them settled*. Two successful records, a successful one beside a failed one in either order, and two failed -ones are all refused. Under a retained installation the history must carry +ones are all refused. Under a saved installation the history must carry exactly one, and it must have succeeded. Malformed, carrying an extra member, written under another name, written by a child coroutine, and naming another run are refused under either installation, whether the history is truncated or completed. A single missing or failed record -is refused under a retained installation and permitted under a programmatic one, +is refused under a saved installation and permitted under a programmatic one, for the reason above. Reading a recorded value is total. A value whose enumeration, property @@ -248,7 +248,7 @@ descriptors, getters or classification refuse describes no run, and becomes the same fixed refusal every other unreadable record becomes — it never escapes carrying its own text. -The history admission reads is the retained history: every discriminator settled +The history admission reads is the saved history: every discriminator settled once, before anything is decided, and the same objects every later phase consumes. An event that refuses any member a decision rests on — its type, its coroutine, either half of its description, its settlement, or a successful @@ -269,7 +269,7 @@ Admission and durable preparation have different jobs in each journal state: | State | What runs | What happens | | --- | --- | --- | -| **live** — no record | the admission, then `prepare` | admission finds no earlier identity; preparation records the supplied retained run, or calls the programmatic host's `allocate()`, before the root is imported | +| **live** — no record | the admission, then `prepare` | admission finds no earlier identity; preparation records the supplied saved run, or calls the programmatic host's `allocate()`, before the root is imported | | **truncated** — record present, root not closed | the admission, then `prepare` | admission restores the recorded run; the preparation's durable operation consumes its recorded result and advances its cursor without allocating again | | **completed** — root `Close` recorded | the admission only | canonical core validates the run and returns the recorded result without entering preparation or expanding the document | @@ -284,7 +284,7 @@ The journal is parsed, never trusted. - A record that does not describe the closed `WorkflowRun` shape is refused. Stored values and unexpected member names are never quoted in diagnostics. -- A retained run whose `runId`, `bundleHash` or exact target differs from the +- A saved run whose `runId`, `bundleHash` or exact target differs from the supplied run is refused, naming the differing fields rather than their values. Both are `StaleInputError`: the journal does not describe the requested run and @@ -298,7 +298,7 @@ document failure or cancellation after that point does not erase it. Failure *before* that point records no `WorkflowRun` value and expands no root document. A programmatic host's `allocate()` may fail while preparing its -inputs; that generic preparation failure is still a durable outcome. A retained +inputs; that generic preparation failure is still a durable outcome. A saved lifecycle run already exists in storage before this installation executes. Such a failure is journaled the way every durable effect's failure is — as a @@ -318,7 +318,7 @@ it (§3.2). Git queries belong to `@executablemd/git`, not workflow definition or run identity. Repository and Git operations are specified in [Workflow Workspace §§6–7](workflow-workspace-spec.md#6-repository-and-worktree); the current query -interface is [GitQueryApi](../packages/git/src/git.ts). Retained definitions +interface is [GitQueryApi](../packages/git/src/git.ts). Saved definitions are read from their own source store (§9.4). `GitQuery.resolve(revision)` has the semantics of @@ -349,10 +349,10 @@ workflow runs may contain the same expansion id; the expansion id does not embed the run id, and expansion identity works with no workflow middleware installed at all. -## 9. Retained run storage +## 9. Saved run storage A run recorded only in the journal of the execution that created it can be -found only by whoever already has that journal. Retained storage is what lets +found only by whoever already has that journal. Saved storage is what lets a second process open a run by its public id and continue from durable data. Each run owns one database. Shared code reaches it through a contextual Api @@ -400,7 +400,7 @@ interface WorkflowDefinition { The descriptor carries **no version and no kind**. One shape is the only shape, so there is nothing for a reader to dispatch on, and a descriptor carrying either member is refused as carrying a member nobody declared. The shape is -closed and admits its exact members in any object-key order. A record retained +closed and admits its exact members in any object-key order. A record saved under an older format carried a `version`, and is therefore refused here rather than guessed at. @@ -423,7 +423,7 @@ interface SourceBundleComponent { logical path, in the UTF-8 byte order of those paths. `entrypoint` names exactly one of them and ends in `.md`. `components`, when present, is non-empty, in the UTF-8 byte order of the names, one entry per name, and every path names a -retained source — so the mapping a root resolves its component names through is +saved source — so the mapping a root resolves its component names through is closed over the same bytes the run executes. Absence means the definition declares no workflow components; an empty array is not a second spelling of that and is refused. @@ -534,7 +534,7 @@ repository-relative path. It may be absent, replaced or become unreachable without changing compatibility or preventing a resume, nothing reads it back to find the source, and failing to obtain it cannot fail a start. The supplied absolute path, the checkout path and the invocation working directory are never -retained in the definition, the run identity, the journal binding or an +saved in the definition, the run identity, the journal binding or an artifact. ### 9.2 Creating a run is also how it is found @@ -568,11 +568,11 @@ A reuse agrees only when the hash algorithm, the bundle hash, the entrypoint, the complete canonical source manifest and component mapping, the exact presence and value of `targetPath`, and the normalized props all agree. The manifest is compared whole even though the bundle hash commits to it: a hash is not a reason -to admit a retained structure that disagrees with itself. The candidate's host +to admit a saved structure that disagrees with itself. The candidate's host path and its optional provenance are ignored, so identical bytes reached through another directory are the same run. -**A compatible reuse executes the retained source, never the newly supplied +**A compatible reuse executes the saved source, never the newly supplied buffers.** `lookup()` finds by id and creates nothing. @@ -609,7 +609,7 @@ read, listed or mistaken for a run. - A nullable **stop reason**: either `{ kind: "host", code }`, a categorical code the host assigns, or `{ kind: "journal", eventId }`, a reference to an event that already crossed the secret filter. Neither shape can carry an - arbitrary exception message, because a message retained this way would be + arbitrary exception message, because a message saved this way would be history nothing had filtered. A journal reason names an event the run holds; one naming an event that is not there is refused, because a reason that refers to nothing is not a reason. @@ -638,7 +638,7 @@ A newly created run keeps `PRAGMA user_version = 1`. Initialization never upgrades an existing database. The declaration is one immutable, closed inventory. `workflow_run` has **no -`base` column**: a run starts from exact retained bytes rather than from a +`base` column**: a run starts from exact saved bytes rather than from a repository state, so a column naming one would be a claim about a repository the run never had. Two tables hold what it executes: `workflow_definition_blob`, keyed by a 64-digit lowercase source hash with its byte length and content, and @@ -653,17 +653,17 @@ is unsupported, and an altered or extra object is corruption — never a migrati candidate. Nothing repairs, migrates or reinterprets a database, and there is no earlier version for a file to be recognized as. -#### What a retained source has to prove +#### What a saved source has to prove The stored manifest equals the descriptor's complete `sources` array: one row per entry and no extra row. Every referenced blob exists, its byte length equals -both retained lengths, and recomputing its source hash produces its key. +both saved lengths, and recomputing its source hash produces its key. Multiple paths may reference one blob when their bytes are identical; unreferenced content is corruption rather than tolerated garbage. Recomputing -the bundle hash from the retained manifest and component mapping produces +the bundle hash from the saved manifest and component mapping produces `bundleHash`. -No reader returns a partial bundle. Before a retained source is parsed as +No reader returns a partial bundle. Before a saved source is parsed as Markdown, the entrypoint and every declared component decodes as well-formed UTF-8 under one strict decoder, which performs no normalization; the BLOB stays authoritative and is what export and every later resume preserve. @@ -687,7 +687,7 @@ nothing. One initialization transaction then writes the descriptor, the manifest rows, the de-duplicated BLOBs, the initial Workspace state, the lifecycle state and the first document-execution record. They all appear or none -do. The transition answers with the authenticated retained closure read back +do. The transition answers with the authenticated saved closure read back from storage, which is what a caller imports. #### 9.4.2 Source is proved before the lifecycle moves @@ -697,7 +697,7 @@ recovery, a new document-execution record, Workspace attachment, journal replay or root import. A failure therefore leaves the run's lifecycle and journal exactly as they were. -For an existing run the retained BLOBs are re-derived in full; the original path +For an existing run the saved BLOBs are re-derived in full; the original path and the provenance are never consulted. A run being created is held to the descriptor it is about to retain before anything is persisted. Completed replay, export and fork read the same store, so none of them reaches a repository. @@ -709,11 +709,11 @@ discoverable by lookup or list. An interruption after the commit is an ordinary resumable execution, because the complete definition already exists. A fresh database contains one content-addressed Workspace root whose canonical -manifest describes only `/` as a directory. Its retained manifest and blob +manifest describes only `/` as a directory. Its saved manifest and blob reference sets are empty, its current-root pointer names that root, and its DOFS frontier contains only the corresponding root directory. -Every later retained root is a complete canonical filesystem checkpoint. Root +Every later saved root is a complete canonical filesystem checkpoint. Root format 1 uses fixed-key-order UTF-8 JSON containing `/` and every reachable absolute POSIX path sorted by UTF-8 bytes. Each entry records its kind, mode and observable mtime; files also record size, their DOFS manifest identity and a @@ -727,10 +727,10 @@ The lowercase root ID is SHA-256 over the stored format and bytes to be identical. File bytes remain only in DOFS blobs. The normalized root-to-manifest and root-to-blob rows equal the exact transitive content of each root and prevent that content from being deleted -while the root is retained. +while the root is saved. Each file entry's declared size equals the size in its referenced DOFS -manifest. Recognition checks that agreement for every file in every retained +manifest. Recognition checks that agreement for every file in every saved root, including roots that are not current, while it validates the manifest's chunks and blobs transitively. @@ -746,7 +746,7 @@ An event's opaque id is stored separately from its physical position. The id is stable once written and is what a journal stop reason points at; the position is what ordering uses and is not a public identifier. -Every journal row also names the retained Workspace root current when the row +Every journal row also names the saved Workspace root current when the row is inserted. Existing non-Workspace appends use the canonical empty current root and otherwise retain their established behavior. @@ -789,7 +789,7 @@ the same canonical path after provider teardown produces a different identity. Every top-level transaction receives a separate opaque identity and an exact active record containing its path, connection generation, open state, authorized lease and transaction handle. The provider invalidates that record -before commit or rollback, so a retained handle or token never becomes valid +before commit or rollback, so a saved handle or token never becomes valid again in a later transaction. Each physical connection holds recovery coordination from opening through @@ -799,7 +799,7 @@ database-and-journal pair. [Read-only lifecycle inspection](../architecture.md#r owns the recovery ordering and connection lifetime; §9.7 owns the storage refusals. -Opening existing storage performs structural recognition, retained-root and +Opening existing storage performs structural recognition, saved-root and content validation, the live/current comparison and the singleton run-row read inside one explicit SQLite read transaction. Those dependent reads therefore describe one committed version. Recognition does not mark the connection as a @@ -967,11 +967,11 @@ writer's transaction is uncommitted, so a crash publishes nothing that was not already visible before it. A later process therefore opens the last committed state and nothing else: the -same live filesystem, the same current root, the same retained roots, manifest +same live filesystem, the same current root, the same saved roots, manifest and blob references, and the same ordered journal with the same event identities and Workspace-root associations. Recorded effects replay rather than execute again, and the private restoration materializer reconstructs any -retained root from that state without the process that wrote it. +saved root from that state without the process that wrote it. The provider-neutral coordinator receives the failure-activation continuation needed for this boundary. The default live coordinator ignores it and preserves @@ -980,7 +980,7 @@ explicit Workspace operation selects the Workspace coordinator. The transaction-bound Files provider of §10 selects it for every document filesystem effect; workflow start and resume do not reach it. -The private restoration materializer loads a fully validated retained root and +The private restoration materializer loads a fully validated saved root and rebuilds directories, files, chunks, modes, mtimes, symbolic links and hardlink relationships inside a nested savepoint. It establishes valid mutable revision state, clears the authoritative DOFS resolution and blob caches, and requires a @@ -988,7 +988,7 @@ read-only resnapshot to reproduce the selected root ID before the savepoint is released. A failure restores the prior live frontier and current-root pointer. Immutable roots are authoritative checkpoints and DOFS tables are the current -live materialization. Every retained root remains indefinitely. The production +live materialization. Every saved root remains indefinitely. The production closure neither exposes nor invokes Cloudflare garbage collection; root-aware deletion and collection are separate lifecycle behavior. @@ -1014,23 +1014,23 @@ differently is reported as itself: | transaction | a transaction cannot be started, continued or committed as asked | | inspection recovery | a crashed run could not be read from a private recovered copy | | definition source missing | the descriptor names a manifest entry or blob the store does not hold | -| definition corrupt | a retained path, length, hash, byte, mapping or bundle hash disagrees | +| definition corrupt | a saved path, length, hash, byte, mapping or bundle hash disagrees | The last two are the source conditions, and they are kept apart because an operator acts on each differently. Missing content is gone and there is nothing to repair; corrupt content is there and no longer describes itself. Neither quotes source, props, retrieval metadata or an absolute path, neither returns partial content, neither advances lifecycle state, and **neither falls back** to -current `HEAD`, working-tree bytes, the original file or the provenance. A malformed retained descriptor or database structure stays a +current `HEAD`, working-tree bytes, the original file or the provenance. A malformed saved descriptor or database structure stays a storage-corruption refusal under the recognition rules above rather than being reclassified as an unavailable external source. Inspection recovery is a distinct condition because it says nothing about the run. It reports that read-only inspection could not produce or clean up the -private copy it reads a crashed run from — the retained database and journal +private copy it reads a crashed run from — the saved database and journal are unchanged, and their next write-capable owner still recovers them. Damage a recovered copy then reveals keeps its own condition above and is never folded -into this one. The refusal names the retained database; it names the private +into this one. The refusal names the saved database; it names the private copy as well, and only, when removing that copy failed and an operator has to remove it by hand. @@ -1056,7 +1056,7 @@ migration candidate. Rows are held to what they mean and not only to their column types: a timestamp is an instant, an identity is not the empty string, and props are an object. -Semantic recognition parses every retained root canonically, recomputes its ID +Semantic recognition parses every saved root canonically, recomputes its ID and exact manifest/blob reachability, validates every referenced manifest, blob, byte payload and live chunk, and requires the read-only live snapshot to equal the singleton current root. Malformed paths or topology, dangling or @@ -1065,7 +1065,7 @@ a file entry whose declared size differs from its referenced DOFS manifest, and a live/current mismatch are damage. Recognition performs no repair. No message repeats a stored value — or a stored *name*. Props and journal -payloads are retained history, and a member name can carry a credential as +payloads are saved history, and a member name can carry a credential as readily as a member value, so an unexpected member is refused without being named and a path through unknown members is written `*`. @@ -1121,7 +1121,7 @@ One effect is one effect transaction. The mutation, the resulting immutable root and the filtered journal result commit together. An ordinary filesystem refusal rolls its mutation savepoint back before its result is published, so a write that created two parent directories and was then refused leaves neither behind, -the retained outcome describes a Workspace that is exactly what it was, the +the saved outcome describes a Workspace that is exactly what it was, the write reports its target as rolled back, and the next effect still commits. What crosses the boundary is a `FilesReason` selected from the shared vocabulary — no DOFS message, errno payload, SQLite text or resolved path. Everything that is @@ -1146,4 +1146,4 @@ uncontained filesystem this boundary exists to prevent. Public `xmd workflow` lifecycle commands; lifecycle transition policy, executor leases and stale-owner recovery; public root selection, history checkpoints and forks; workflow-owned worktrees; and deterministic Git and GitHub effects. -Retained roots and private restoration do not expose any of those behaviors. +Saved roots and private restoration do not expose any of those behaviors. diff --git a/specs/workflow-workspace-spec.md b/specs/workflow-workspace-spec.md index 386d7ddfb..f5eb5e84c 100644 --- a/specs/workflow-workspace-spec.md +++ b/specs/workflow-workspace-spec.md @@ -4,12 +4,12 @@ - **Audience:** Workflow host, provider and component implementors This specification defines the observable contract of `xmd workflow`: a -provider-neutral retained environment in which readable Markdown procedures run +provider-neutral saved environment in which readable Markdown procedures run as repeatable workflows. It complements `architecture.md`, which owns the implementation invariants. Start with [A document running](../architecture.md#a-document-running) for the ordinary/workflow and replay model. This document owns lifecycle commands, workflow profiles and observable behavior; [Workflow runs](workflow-spec.md) -owns retained identity and storage, and the [Executable MDX specification](executable-mdx-spec.md) +owns saved identity and storage, and the [Executable MDX specification](executable-mdx-spec.md) owns authored syntax, bindings and ordinary component behavior. ## 1. First workflow @@ -108,7 +108,7 @@ on every runtime. ### 2.2 `xmd workflow` -`xmd workflow` executes supported operations against a retained constrained +`xmd workflow` executes supported operations against a saved constrained environment. - Every run owns an implicit root Workspace. @@ -171,7 +171,7 @@ whatever the same glob would name against a later checkout. Every source check happens under the executor lock and **before** stale recovery, a new document-execution record, Workspace attachment, journal replay -or root import. A run's retained content is re-derived from its own store, so no +or root import. A run's saved content is re-derived from its own store, so no host supplies a source capability and nothing reaches a repository to begin, fork or stage a run. @@ -237,7 +237,7 @@ xmd workflow start prepare-release.md Each invocation streams its rendered document output to standard output while executing. Run identity and lifecycle metadata go to standard error. -`resume` names the exact retained run and supplies neither the document nor its +`resume` names the exact saved run and supplies neither the document nor its root props again: ```sh @@ -256,7 +256,7 @@ source-bundle run by its entrypoint and bundle hash, with its exact target when it has one, and reports no base and no fabricated commit for it. `start` executes until completion, failure, suspension or interruption. -`resume` continues an interrupted or ready suspended run under its retained +`resume` continues an interrupted or ready suspended run under its saved definition and normalized props. A document path locates a definition; it never selects a previous run implicitly. @@ -294,7 +294,7 @@ xmd workflow start \ Starting an unused ID creates its run. Compatible reuse addresses the same run. The immutable definition and normalized props must agree; incompatible reuse -fails. A completed compatible run performs a full replay and emits its retained +fails. A completed compatible run performs a full replay and emits its saved output without attaching the Workspace or external providers. Only one workflow executor advances a run. It acquires one scope-owned executor @@ -330,7 +330,7 @@ An unfinished execution left after its host dies is not stale because time passed or a PID disappeared. A new workflow executor proves staleness by acquiring the released executor lock. For a `running` run it atomically closes the exact execution and publishes `interrupted` before beginning another when -retained history has no root Close. A Close restores its canonical terminal +saved history has no root Close. A Close restores its canonical terminal state. A `cancel` command that acquires the executor lock instead publishes `cancelled` without beginning another execution, also only when no root Close already won. @@ -357,11 +357,11 @@ The initial statuses are: Expected transient recovery belongs in document middleware or an effect policy. An uncaught root failure is terminal. Reusing its compatible ID replays the -retained failure rather than silently retrying it. A corrected document starts +saved failure rather than silently retrying it. A corrected document starts a new run or an eligible history fork. A host-settlement failure may have no -root Close; compatible reuse reports that retained failed state and reason +root Close; compatible reuse reports that saved failed state and reason without entering replay. Neither kind is advanced under the same ID. -A cancelled run reports its retained state without entering replay under either +A cancelled run reports its saved state without entering replay under either command. ### 3.5 Suspension releases the executor lock @@ -389,7 +389,7 @@ copies through stable contextual routing. That route carries a suspension notice, not an answer: a middleware handler may refuse or suppress its own descendant's call, but returning a value cannot make an unanswered `suspendFor()` continue. The executor accepts a notice only at the exact current -durable position of the matching retained request. +durable position of the matching saved request. With no delivered input, `suspendFor()` remains pending while the workflow host halts the document execution as structured control flow. It throws no document @@ -406,7 +406,7 @@ host installs beside the composition components, for a live or partial attachment only; it asks the same question core's does and writes no durable record of its own, because a durable wait cannot sit inside another durable operation. The host's provider calls `suspendFor()` with the rendered message as -the request and the compiled schema as the response schema, so what is retained +the request and the compiled schema as the response schema, so what is saved is what the person was asked, and a delivered value is judged against the schema the document itself compiled. An `` region installs nearer and answers first, so a document that already knows the answer never suspends. `resume` @@ -419,7 +419,7 @@ Providers remain lazy and are not contacted for replayed effects. #### Typed answer delivery Delivery is an operation separate from execution. Its public correlation -boundary is the retained suspension ID and response schema: +boundary is the saved suspension ID and response schema: ```sh xmd workflow answer [--secret-detection|--no-secret-detection] @@ -436,9 +436,9 @@ workflow answer: () ``` A value is accepted only when the run is `suspended`, its stop reason names a -retained `suspension_request` event carrying the supplied suspension ID, and the -value satisfies the response schema that request retained. Secret detection -applies to the retained state and the answer event it would become, on the same +saved `suspension_request` event carrying the supplied suspension ID, and the +value satisfies the response schema that request saved. Secret detection +applies to the saved state and the answer event it would become, on the same terms as durable journal persistence, and is on unless `--no-secret-detection` disables it for that delivery. Duplicate, consumed, wrong-run, wrong-request, late, invalid, cancelled-run and missing-run delivery is refused, and a refusal @@ -447,12 +447,12 @@ nor a secret match in its diagnostic. `resume` keeps its grammar: exactly one run id, no answer flag and no untyped generic answer. On a later explicit resume, `suspendFor()` continues only from -retained pending answer state for the exact current unanswered request, and +saved pending answer state for the exact current unanswered request, and publishes one ordinary durable `suspension_answer` Yield before returning the value to the document. Consuming that pending state and appending that Yield are one database transaction: a crash or an injected failure before it commits leaves the answer pending and publishes nothing, and replay after it commits -restores the retained answer event without consuming or publishing again. +restores the saved answer event without consuming or publishing again. This suspension and executor-lock-release contract folds issue #322 into #367; #322 is not a separate implementation prerequisite. Scheduling — automatic @@ -473,12 +473,12 @@ Explicit cancellation makes a run without a live workflow executor terminal. It retains the journal and Workspace for inspection, training and an eligible history fork. It does not undo completed local or external effects. -Cancellation follows the retained status: +Cancellation follows the saved status: - `running` with a live workflow executor refuses without mutation and tells the caller to interrupt the foreground process; - stale `running` acquires the executor lock and becomes `cancelled` when no - retained root Close already proves completion or failure; + saved root Close already proves completion or failure; - `suspended` and `interrupted` acquire the executor lock and become `cancelled` without starting an execution; - `cancelled` succeeds idempotently; and @@ -490,7 +490,7 @@ unfinished stale execution when one exists, and publishes `cancelled` atomically. No live scope is halted, watched or polled by another process, and there is no request or acknowledgement to outlive either process. A lifecycle storage refusal leaves -the retained state unchanged and reports no uncommitted terminal status. +the saved state unchanged and reports no uncommitted terminal status. Ctrl-C remains foreground interruption: the workflow executor attempts every finalizer, publishes `interrupted` and releases the executor lock. A teardown @@ -517,7 +517,7 @@ no status line. **What an execution reports.** `start`, `resume` and `fork` each publish two lines on standard error: the run identity once the run exists, and the status -once it is retained. +once it is saved. ```text workflow run: @@ -535,13 +535,13 @@ workflow status: suspended ``` The suspension line is published on the same terms as the status line: after the -settlement transition commits, never before. A wait nothing retained is not one +settlement transition commits, never before. A wait nothing saved is not one a caller can answer, and a refused settlement publishes neither line. The -identifier is the one the retained `suspension_request` event carries, so what +identifier is the one the saved `suspension_request` event carries, so what the caller is told to answer and what the run is waiting on are the same suspension. The status line stays the closing word on every outcome. -**A status line says what was retained**, so a lifecycle transition storage +**A status line says what was saved**, so a lifecycle transition storage refused publishes none. Finishing the document-execution record and publishing the run state are one transaction; no command observes one without the other. What a refusal costs the exit code depends on when it happened: @@ -557,7 +557,7 @@ What a refusal costs the exit code depends on when it happened: `interrupted` rather than saying something untrue about it. In both cases the refusal is authoritative and stops what depends on it. Only -the status and a categorical host reason are retained; a storage diagnostic +the status and a categorical host reason are saved; a storage diagnostic reaches the caller and is never written into the run. Management commands report their own request. `workflow cancel ` exits zero @@ -611,7 +611,7 @@ line is the closing word either way. A status line is published only after the transaction that finishes the document-execution record and publishes the run state has committed. What a refusal costs the exit code is §3.7's; what it costs the *record* is the same -everywhere: neither half is retained, the unpersisted status is neither +everywhere: neither half is saved, the unpersisted status is neither published nor claimed, and a document failure that also occurred is still reported. @@ -627,7 +627,7 @@ Source is proved first, under the executor lock and before every one of those (§3.1), so a run whose source cannot be obtained is refused for that with its lifecycle and journal untouched. Reusing a compatible id through `start` is a separate rule: it replays a failed run's -retained failure, and that does not make the run eligible for `resume`. +saved failure, and that does not make the run eligible for `resume`. - `start` takes exactly one Markdown document reference — a path, optionally followed by `#` and one target selector, with a literal `#` in a filename @@ -643,7 +643,7 @@ retained failure, and that does not make the run eligible for `resume`. - `fork` takes the run it continues, the committed event it continues from and the definition it continues with, and every one of the three is required. Its generated `--props-*` arguments are the candidate definition's and merge over - the props the source retained, and `--at` belongs to it alone (§11). + the props the source saved, and `--at` belongs to it alone (§11). - The document filesystem (§10.1), Repository/Worktree/Dir composition (§6), and the constrained ACP-only Agent profile (§8) are capabilities a run has. Git operations, native Agent session launch, Worker Shell and native services @@ -666,7 +666,7 @@ Runs live beneath `~/.xmd/runs` unless `XMD_WORKFLOW_RUNS` names another absolute directory. Where a run's database is on a host is arrangement, not identity (§5.2). -Status, list and history for retained runs are built (§4), and so are cancel and +Status, list and history for saved runs are built (§4), and so are cancel and delete, and export. Artifact export and artifact status/history are built; the artifact-backed fork is specified in `specs/xmd-artifact-spec.md` and remains unbuilt. @@ -681,7 +681,7 @@ same wait. Typed answer delivery is what ends such a wait, and `xmd workflow answer` is built by #300; scheduling is not, so a wait ends when somebody resumes the run rather than on its own. Fork is built (§11): a run's history can be continued under a changed definition from any committed checkpoint the -retained events allow. +saved events allow. The component bundle is built: a root declares one, `start` reads it from beside the root, the definition retains its bytes, and `resume` and completed replay @@ -720,7 +720,7 @@ Human output is the default; `--json` returns the same data structurally. An artifact source first passes the complete read-only recognition and manifest validation in `specs/xmd-artifact-spec.md`. It then projects the same lifecycle snapshot and history shapes, with the artifact identity and artifact frontier -added. Artifact inspection never makes the file a discovered retained run and +added. Artifact inspection never makes the file a discovered saved run and never creates a SQLite sidecar. The projection is the same whichever profile the artifact holds. An artifact @@ -740,7 +740,7 @@ though it were complete, and it changes no candidate. A crash does not prevent read-only inspection of the last committed state. When SQLite requires rollback recovery, inspection reads a privately recovered -copy and leaves the retained database and journal unchanged. It processes one +copy and leaves the saved database and journal unchanged. It processes one candidate at a time and returns success only after removing its scratch copy. Failure to produce or remove that copy is an inspection-recovery refusal, separate from damage revealed in the recovered run. Recovery obtains neither an @@ -755,7 +755,7 @@ xmd workflow history release-1.4 --forkable xmd workflow history release-1.4 --json ``` -`history --json` exposes every retained protocol event in append order and its +`history --json` exposes every saved protocol event in append order and its stable public event ID. `fork --at` accepts eligible IDs under §11. Human history presents durable operations without repeating protocol event kinds: @@ -768,11 +768,11 @@ EVENT OPERATION SOURCE RESULT Outcome: completed at 3c9f…, Workspace 4c5d… ``` -The identifiers are the retained event and Workspace-root identities, shortened +The identifiers are the saved event and Workspace-root identities, shortened here for the page. A row with no authored source shows that it has none rather than a location the command derived. -Each entry contains the exact retained event ID and protocol event, the +Each entry contains the exact saved event ID and protocol event, the Workspace root associated with that row, its forkability, an optional normalized authored source, and — for a row a fork inherited — the source run and source event it came from. Yield descriptions carry it under the stable namespaced @@ -793,7 +793,7 @@ visible with the corresponding coroutine. A suspended, interrupted or cancelled run with no root Close says that no canonical document outcome was recorded. History never reconstructs a filtered value from the Workspace or provider, materializes the associated root, mixes rendered stdout into the -trace, removes a retained event from JSON, or synthesizes a journal event. +trace, removes a saved event from JSON, or synthesizes a journal event. `--json` always includes `forkability`, and `--forkable --json` returns the same JSON. `--forkable` adds `FORKABLE` and `BLOCKERS` to human output and keeps @@ -815,7 +815,7 @@ introduced it, under one of four stable codes: `workspace-root-unavailable`, `unsupported-effect`. An effect type is inheritable because it is recognized, never because it failed to match something, so history a later build wrote reports `unsupported-effect` rather than a checkpoint this build is guessing -about. Blocker output carries stable codes and retained event IDs and nothing +about. Blocker output carries stable codes and saved event IDs and nothing else: no filtered values, props, provider diagnostics or secrets. Candidate definition and prop divergence are reported by `fork`, not here. @@ -859,7 +859,7 @@ The run's own record is the part of that database the lifecycle reads. It holds the immutable definition and the normalized props; the current status and its stop reason; one document-execution record per start and per resume; replaceable retrieval metadata; and the filtered journal. It holds no base and no pinned -commit: a run starts from exact retained bytes rather than from a repository +commit: a run starts from exact saved bytes rather than from a repository state, so a member naming one would be a claim about a repository the run never had. @@ -867,7 +867,7 @@ The immutable definition is a source bundle: a hash algorithm, a bundle hash, a logical entrypoint, and a canonically ordered manifest holding each source's logical path, its domain-separated content hash and its byte length. Its component bundle maps each declared name onto a logical path the same manifest -retains. The bytes behind every path are retained with the run, so nothing has +retains. The bytes behind every path are saved with the run, so nothing has to be fetched at all; an optional credential-free provenance observation lives in a replaceable metadata boundary, free of credentials, reauthorized by the host before use, excluded from the comparison that decides whether a reused run @@ -883,7 +883,7 @@ component bundle, and the normalized props, canonically. The comparison covers the bundle hash, the entrypoint, the complete canonical source manifest, the component mapping and the exact presence and value of the target — the manifest whole, even though the bundle hash commits to it, because a hash is not a reason -to admit a retained structure that disagrees with itself. +to admit a saved structure that disagrees with itself. A changed component name, path or component set conflicts as `definition`, and the refusal names that field rather than any value behind it. Status, stop reason, retrieval metadata, provenance, @@ -891,7 +891,7 @@ timestamps, document executions and journal records are excluded, so a completed run asked for again is found rather than refused. A stop reason is a categorical host code or a reference to an already-filtered -journal event. Arbitrary failure text is not retained beside the journal that +journal event. Arbitrary failure text is not saved beside the journal that filtered it, and `history` therefore exposes no value the journal's security policy has not already seen. @@ -913,7 +913,7 @@ retried operation or region and belongs to the journal. A run is found by its public ID alone, without consulting a second registry. The database's location on a host is arrangement rather than identity, so the -run ID is also retained inside the run and checked: storage holding a different +run ID is also saved inside the run and checked: storage holding a different run than its location implies is reported as a distinct failure. Absent, conflicting, foreign, version-incompatible, damaged and unparseable @@ -929,7 +929,7 @@ providers behind it (§2.1). What an author writes — the forms, the props, wha `as` binds, and the refusal vocabulary — is the same either way; what differs is what a checkout *is*. -Under a workflow run a checkout is retained Workspace state: creation identity +Under a workflow run a checkout is saved Workspace state: creation identity is a durable effect, placement is Workspace-relative, and a completed effect restores from the journal. That is what the rest of this section describes. @@ -939,7 +939,7 @@ by a closed version 1 sidecar and held for one document execution by an exclusive non-blocking advisory lock; reuse compares creation identity alone and never resets, cleans, fetches, repairs or deletes; and a Worktree written outside a lexical `` belongs to the ambient Repository the command -was run in. Nothing there is retained, replayed or forkable, and none of the +was run in. Nothing there is saved, replayed or forkable, and none of the durability this section states applies to it. What both share is the composition value a component observes: a Repository @@ -1030,7 +1030,7 @@ A locator is admitted before it is used, and admission is an allowlist: an `https`, `http`, `ssh`, `git` or `file` URL carrying no userinfo, Git's `user@host:path` form, or an absolute local path. A locator carrying a credential is refused rather than rewritten, because rewriting it would retain a -run nobody asked for. What is retained beside the record is the admitted locator +run nobody asked for. What is saved beside the record is the admitted locator itself; what travels through the journal, a binding or a diagnostic is its fingerprint. @@ -1126,7 +1126,7 @@ only cwd. It neither selects nor replaces the Repository in scope, so a Git operation inside a directory beneath a selected checkout still addresses that same Repository. -Under the interactive `xmd repl` the ensure is retained where the target is +Under the interactive `xmd repl` the ensure is saved where the target is inside the checkout the entry was started in, so continuing an interrupted entry answers a completed ensure from its record and asks the provider for nothing (`specs/repl-spec.md`). Everything stated above is unchanged by that: the ensure @@ -1170,7 +1170,7 @@ describes. Under an ordinary `xmd run` the same authored transitions happen directly against the selected checkout: no transaction encloses them, nothing rolls back, nothing replays, and no such claim is made. Section 7.3's fixed Git identity is a workflow run's, for a reason that inverts here — a workflow's -retained state must not depend on whose machine made it, and an ordinary run's +saved state must not depend on whose machine made it, and an ordinary run's commit lands in that person's own checkout. So an ordinary commit records the invoking user's own effective identity, and a host that can name none refuses rather than substituting one. Push keeps the @@ -1216,10 +1216,10 @@ written in `` still operates on the whole checkout tha directory belongs to, and that directory is only where the operation runs. Neither observation admits anything. The Repository is compared with the row -the run retained under that name, member for member, and the working directory +the run saved under that name, member for member, and the working directory has to be a real directory inside one of the checkouts the run retains for it. -No enclosing `` at all, a Repository that is not the retained one, a -directory inside no retained checkout, and retained state that no longer agrees +No enclosing `` at all, a Repository that is not the saved one, a +directory inside no saved checkout, and saved state that no longer agrees with the identity naming it are failures of the run rather than outcomes: no Git runs, nothing is published, and `` cannot print them. The same is true of a native Git failure the provider has no word for — the refusals are a @@ -1231,7 +1231,7 @@ evidence: the checkout it ran in, the requested and resolved branch, the requested base and the commit a created branch actually started from, and the branch, commit, HEAD tree and index tree the checkout held before and after. -A retained result is read back for the invocation that recorded it, and one whose +A saved result is read back for the invocation that recorded it, and one whose identity, branch, base or transition does not describe that invocation is damage rather than history. A Worktree's name and its path are one identity, held to the placement that produced them; and switching to the branch a checkout is already @@ -1260,7 +1260,7 @@ than read as "here" or as "everything". A pathspec must be well-formed text. A string is UTF-16 code units and need not be well-formed, but the way to Git is UTF-8, and an unpaired surrogate becomes U+FFFD in transit — so what Git received would differ from what the run -retained. A pathspec holding one is refused as malformed input, before any effect +saved. A pathspec holding one is refused as malformed input, before any effect exists and before Git runs, wherever the request enters: a document's element and a direct call on the composition Api are held to the same boundary. Nothing is normalized or respelled to make one fit, and every well-formed string — U+FFFD @@ -1276,11 +1276,11 @@ that contains it. Add renders nothing, binds nothing and takes no content. What it retains is evidence: the checkout it ran in, the pathspecs exactly as they were given, and the branch, commit, HEAD tree and index tree the checkout held before and after. -Staging moves the index and nothing else, so a retained result whose branch, +Staging moves the index and nothing else, so a saved result whose branch, commit or HEAD tree changed describes a transition Add does not make, while its index tree may have moved or not — staging what is already staged changes nothing. What Git matched is not enumerated: naming files it discovered would put -content beyond the document's own pathspecs into retained history. +content beyond the document's own pathspecs into saved history. Failure follows §7.1, with four refusals of its own. A pathspec that matched no files, one that selects an ignored path — Add has no force control — one that @@ -1334,7 +1334,7 @@ run retains is a digest of the bytes it committed. **Time.** The provider captures one instant while the effect runs, truncates it to whole seconds and records it as both author and committer time. There is no -prop and no caller-supplied time, and a replay restores the retained second +prop and no caller-supplied time, and a replay restores the saved second rather than reading a clock. **What it commits.** Exactly the index. There is no `paths`, no implicit staging, @@ -1349,7 +1349,7 @@ length of its exact bytes, the parent, tree and commit object ids, the second it recorded, and the branch, commit, HEAD tree and index tree the checkout held before and after. -A retained result is read back for the invocation that recorded it. Beyond that +A saved result is read back for the invocation that recorded it. Beyond that evidence it describes an object graph a commit of the index can have: exactly one parent, which is the commit the checkout was on; a tree that is the one the index described; and a checkout that ends on that commit with both HEAD and the index @@ -1439,8 +1439,8 @@ Push is explicit. Neither Commit nor PullRequest publishes implicitly. nothing to publish. It is decided from the checkout's own state, before a Git-host effect exists and before any remote is contacted, and it is a `unnamed-branch` refusal a document can act on. Failure otherwise follows §7.1: -no Repository in scope, a Repository that is not the retained one and a -directory inside no retained checkout are failures of the run. +no Repository in scope, a Repository that is not the saved one and a +directory inside no saved checkout are failures of the run. **What reaches the Git host, and how.** The network operation never runs through the selected checkout's own `.git/config`. That file is inside the Workspace the @@ -1450,7 +1450,7 @@ signing program are all things it could then name. The provider instead runs the transport in a disposable repository of its own, configured by nothing but its own creation, which reads the selected checkout's object database through a read-only alternate — the authenticated private object-source attachment of -§10.2. The transport destination is the exact private retained locator, not a +§10.2. The transport destination is the exact private saved locator, not a mutable `remote.origin.url` or `pushurl`, and the push carries one explicit refspec, no upstream option and no force option. @@ -1498,11 +1498,11 @@ The pre-state is what the destination held, in one of exactly three shapes: ``` The relation is the provider's attestation, not the traversal that produced it: -no Git object, path or command output is retained for it. The observations and +no Git object, path or command output is saved for it. The observations and the result name the commit this attempt published and nothing else, so what advanced and what it advanced from are read from two different members. -A retained record is read back for the invocation that recorded it. Beyond the +A saved record is read back for the invocation that recorded it. Beyond the shape it describes a reconciliation this operation can reach: the remote is `origin`, the destination is that branch's ref, the refspec is that commit published to that destination, the observed commit is the one published, and the @@ -1553,7 +1553,7 @@ that checkout is on and there are no `repository`, `head`, `remote`, `provider`, Content expands first and completely. A body that never finished rendering stops everything this component owns, before a Repository is observed, a checkout is -selected, retained history is read or a Git host exists in the story. +selected, saved history is read or a Git host exists in the story. **PullRequest is an upsert over one explicit pull-request identity.** Without a number the document asks for a pull request from this head to this base: one is @@ -1592,7 +1592,7 @@ last one at another commit is conflicting rather than missing, because saying Missing, conflicting and unreadable evidence are three fixed local refusals: each names its category and, where there is one, its remedy, quotes no journal content, and happens before the Git host is observed. So do a missing or -replaced Repository context, a working directory inside no retained checkout, +replaced Repository context, a working directory inside no saved checkout, and a checkout whose HEAD names no branch. None of them is printable: a later sibling must not run as though a pull request had been opened or updated. @@ -1617,7 +1617,7 @@ name different external resources: The complete request fingerprint covers every input, so a changed title, body, draft flag, base, head commit or number diverges at the durable position rather -than consuming the result retained for another question. In create mode, a +than consuming the result saved for another question. In create mode, a branch pair with no open pull request and no closed or merged one is absent and is created once; one whose single open pull request agrees with every field the request names is adopted; one that disagrees, or holds a closed or merged pull @@ -1625,11 +1625,11 @@ request, is a conflict; more than one open pull request is ambiguous, even if one of them fits. In update mode, the numbered pull request is fetched exactly: one that already holds every requested field is adopted as a no-op, and one that differs in a mutable field is the requested completion being absent, with the -existing pull request retained as the pre-state that a performed update +existing pull request saved as the pre-state that a performed update describes itself against. **The first adapter** works over `github.com`, on direct same-repository pull -requests. It is selected from the private retained locator — the admitted +requests. It is selected from the private saved locator — the admitted credential-free HTTPS, SSH-URL and `git@github.com:owner/repository` forms, with an optional terminal `.git` — and a Repository this adapter does not recognize is refused as an unsupported effect kind from observation, before any remote @@ -1739,7 +1739,7 @@ mutates a remote and must adopt an interrupted attempt rather than repeat it. These change nothing, have no natural key and no pre-state, and are ordinary non-mutating durable reads. An interruption before the result commits may repeat the requests, which is safe for a read in the way it is not for a write; no -incomplete list is ever retained. A completed replay restores the retained array +incomplete list is ever saved. A completed replay restores the saved array without opening an authentication session or sending anything. **Complete, or unavailable.** The host follows the provider's pagination until @@ -1764,23 +1764,23 @@ document names a URL, never a host or a credential. Absence of that configuration authorizes no URL read and disables nothing else: `` upserts on this run's own matching Push evidence, which no configuration grants or withdraws. Nothing of the credential, the endpoint, the raw -response or the pagination state is retained: what the journal holds is the +response or the pagination state is saved: what the journal holds is the complete normalized request and the normalized evidence, across the ordinary -secret gate. The retained request is the whole normalized question: the operation, the +secret gate. The saved request is the whole normalized question: the operation, the canonical URL, the provider discriminator, the collection, the run and the expansion. The effect's name covers the question rather than the run — a fork is a different run reaching the same position with the same question, and a name -carrying the run would make every inherited read a different effect. A retained +carrying the run would make every inherited read a different effect. A saved read *is* inheritable: it changed nothing at the host, so a fork consumes the record and asks the provider nothing. -The retained result is a discriminated envelope: `{ kind: "reviews", items }`, +The saved result is a discriminated envelope: `{ kind: "reviews", items }`, `{ kind: "comments", items }`, or `{ kind: "checks", headSha, items }`. Only a checks result has a head, so the other two carry no member a reader must know to ignore. Only `items` becomes the component's binding. The records are closed and provider-neutral. Bodies and provider messages are -retained byte-for-byte, because a reviewer reading a summary of an objection has +saved byte-for-byte, because a reviewer reading a summary of an objection has not read the objection; identifiers are decimal strings, because a provider identifier already exceeds what a JSON number holds exactly; and timestamps are the provider's own strings, unreformatted. Check runs and commit statuses keep @@ -1833,7 +1833,7 @@ native tool set, and the permission mode is `deny-all` with non-interactive denial. A fixed session instruction layer states that boundary to the Agent in the same -terms, and states all of it: no native tool is authorized; retained work can be +terms, and states all of it: no native tool is authorized; saved work can be asked for only when the prompt being answered asks for it and only in the exact closed shape that prompt supplies (§8.4); and nothing the Agent returns carries permission, because source it writes is data this run may admit under ceilings it @@ -1846,7 +1846,7 @@ with a fixed diagnostic that names nothing the request carried. It does not reach the public permission chain, so an authored `` composes around nothing: there is no native tool permission for it to widen. -That directory is attachment arrangement rather than retained state. It does not +That directory is attachment arrangement rather than saved state. It does not identify a logical session, is never imported into the Workspace, and is recreated empty on the next attachment. Provider caches and provider-owned session state live in the run's own provider-session sidecar (§8.5), which the @@ -1862,7 +1862,7 @@ foreground launcher, so a launch is refused before provider preparation, session ownership transfer, or process creation. The host does not expose the machine-wide coordinator, construction-route store or executable observer to repair that refusal. Supporting an interactive workflow-native handoff would -require a separate retained-host and permission contract; it does not weaken the +require a separate saved-host and permission contract; it does not weaken the current sandbox. ### 8.4 Generated XMD @@ -1904,7 +1904,7 @@ selected spellings all read refuses `allow={["write"]}` outright. #### Admission -The evaluator is given the exact candidate source, the retained implementations +The evaluator is given the exact candidate source, the saved implementations each selected name resolved to with the forms it was selected for, and the classes this fragment draws on. It is given no Workspace basis and no request inventory: what the run stands on and what a request may reach are both live @@ -1950,11 +1950,11 @@ so does what the run retains of it, so a rejected fragment publishes nothing — including a credential a rejected element carried. A request the fragment malformed is the component's own refusal, reported where the request is made: that is after the admission, which is the line past which the exact source is -retained on purpose, so there is no pre-admission diagnostic for it to escape +saved on purpose, so there is no pre-admission diagnostic for it to escape through. -Admission is the line. Before it nothing of the candidate is retained; after it -the exact source is retained deliberately, so an ordinary expansion diagnostic +Admission is the line. Before it nothing of the candidate is saved; after it +the exact source is saved deliberately, so an ordinary expansion diagnostic naming part of the fragment discloses nothing the journal does not already hold. @@ -1967,7 +1967,7 @@ construct class and nothing else. It carries no Workspace basis and no request inventory: both are live, and a grant compared against a copy of either would refuse a run for its own progress. The whole event crosses the journal's secret filter like any other, and it commits before the first generated effect. The effects -themselves are retained by their ordinary records — `fetch` for ``, +themselves are saved by their ordinary records — `fetch` for ``, `workspace_file` for `` (§10.1) — and the admission and every nested generated effect are offered inline by the owning document expansion, in authored order and ahead of the expansion's later effects, so one durable @@ -1980,11 +1980,11 @@ or a provider anything. An interruption before an observation's own record commits retains no partial observation; a later continuation may perform that read once more under the same -retained admission. +saved admission. #### A continuation is held to the selection it was admitted under -A continuation uses the exact source and selected policy retained by the +A continuation uses the exact source and selected policy saved by the admission, not a newly supplied candidate. It checks them before invoking a generated component or performing a request. @@ -1993,11 +1993,11 @@ admitted spellings with the identity each runs under and the class that admitted it. Only the classes the selection reached participate, so changing a writing spelling a read-only admission never drew on does not invalidate it. [Generated admission and continuation](../architecture.md#agent-permissions-and-generated-xmd) -owns the retained policy's shape. +owns the saved policy's shape. The run's own progress is deliberately not a term. A committed mutation retains another immutable root and advances the frontier, and a continuation compared -against a retained copy of that frontier would refuse a run for exactly the work +against a saved copy of that frontier would refuse a run for exactly the work it was admitted to do. So is a destination policy: it is asked live, where the request is performed. @@ -2152,7 +2152,7 @@ it runs under — and core's self-closing ``. A host's own extensio are names it answers for, and the selection offers no external-write and no execution class at all. -The retained rows are the identity, in the canonical order the selection is +The saved rows are the identity, in the canonical order the selection is admitted in rather than in whatever order a host's object happened to enumerate. A continuation compares them row by row ([§8.4, continuation](#a-continuation-is-held-to-the-selection-it-was-admitted-under)), so a run admitted @@ -2173,7 +2173,7 @@ owns atomicity, failure and cancellation. Selecting `allow={["write"]}` therefore intentionally authorizes persistent, recursive directory creation by the admitted ``. A continuation whose -retained rows hold another identity, spelling or effect behind that name refuses +saved rows hold another identity, spelling or effect behind that name refuses before any generated component executes; the write ceiling is not silently broadened. @@ -2202,7 +2202,7 @@ Workflow-bundled Markdown component admission is unbuilt. Directory registration is not among them: §8.1 is the contract, and a workflow Agent is given no directory to register. -A final proposal is the retained response of its Prompt and nothing more. It is +A final proposal is the saved response of its Prompt and nothing more. It is returned as source data; the Prompt executes none of it, and a proposal that describes a mutation describes one nobody has made until an authored element admits it. Any later execution of those mutations crosses the mutation-proposal @@ -2212,7 +2212,7 @@ channel for it to cross instead. ### 8.5 Agent session continuity The filtered workflow journal and the provider-owned Agent session are separate -retained assets. The journal preserves observable prompts, responses, tool +saved assets. The journal preserves observable prompts, responses, tool events and outcomes for replay and training. It does not claim to reconstruct provider-internal summaries, hidden context or tool state. @@ -2241,7 +2241,7 @@ served here is served over ACP alone. Composing the two is a separate design. **The mapping lives in the run.** Its database row commits in the run's transaction and is part of the initial schema. Provider session stores and empty -working directories beside the run are disposable attachment state, not retained +working directories beside the run are disposable attachment state, not saved workflow identity. **First-turn publication follows the mapping commit.** Placement is inert: @@ -2272,15 +2272,15 @@ host resolves the same identity under a new empty working directory: No refusal starts a replacement session, and none injects a transcript while claiming conversation continuity. -Deleting a run removes its retained storage — the mapping with it, because the +Deleting a run removes its saved storage — the mapping with it, because the mapping is in it — and its provider-session directory, under the executor lock, and reports exactly the categories that went. A refused deletion removes neither. **A sealed artifact may carry detached portability evidence about these sessions.** Its format classifies every logical Agent session that contributed a -retained Prompt as portable — with ordered provider checkpoint tokens and an +saved Prompt as portable — with ordered provider checkpoint tokens and an opaque Agent session bundle — or as explicitly unavailable, under -`specs/xmd-artifact-spec.md` §2.5. That evidence is detached: it is retained +`specs/xmd-artifact-spec.md` §2.5. That evidence is detached: it is saved bytes about a conversation, never a live provider session, never control over the host that issued a token, and it changes nothing above. The mapping, the canonical assertion, reattachment, replay and the disposable session directory @@ -2314,12 +2314,12 @@ at once supplies no checkpoint and changes nothing else about the completion. outside any transaction; a transaction opens only to publish it. That transaction appends the ordinary `agent_prompt` event, names the exact event that append produced, retains the checkpoint against it and the run's own -retained session key, and commits. Both halves survive or neither does: an +saved session key, and commits. Both halves survive or neither does: an association that outlived a Prompt that was never journaled would name a turn the run has no record of, and a Prompt whose association was lost would become one this run cannot continue from without saying so. -The retained session key is the run's own, supplied by the placement that made +The saved session key is the run's own, supplied by the placement that made the session. It is never recovered from the spelling of the provider's placement key: those are two namespaces that happen to share a prefix. @@ -2344,7 +2344,7 @@ a workflow run executes the snapshots this build carries for those two providers ordinary agent registry, not a replacement for it. Every other agent resolves to the same command, and retains the same compatibility identity, as it did before any adapter was carried: a run may use one, it completes normally, and a session -it retained under an earlier version stays the session it was. +it saved under an earlier version stays the session it was. The override is qualified rather than best-effort. For Codex and Claude the snapshot is verified against its recorded digest and materialized before the @@ -2358,7 +2358,7 @@ when a provider returns to a published release. **An agent that names no turn is not a failed Prompt.** A run whose agent reports no checkpoint — because this build overrides nothing for it, or because the metadata was missing or unreadable — completes exactly as it always did. The -`agent_prompt` event is journaled, the Prompt result is retained and replays +`agent_prompt` event is journaled, the Prompt result is saved and replays from it, and the run simply holds zero associations. Only the association is absent; nothing about the completion is. @@ -2371,12 +2371,12 @@ them, not in them. Replay reaches none of this. A completed or partial replay answers from the stored Prompt result alone — it contacts no provider, opens no publication transaction and associates nothing — and a Prompt replayed from a run that -retained no checkpoint remains unassociated. +saved no checkpoint remains unassociated. ## 9. Replay and continuation Everything in this section is workflow-only. An ordinary `xmd run` has no -WorkflowRun, no retained history and nothing to replay: its repository +WorkflowRun, no saved history and nothing to replay: its repository operations are performed once per execution, and a second run is a second question rather than a continuation of the first. @@ -2397,23 +2397,23 @@ capturing the root the run already had. | Construct | Partial replay behavior | | --- | --- | | implicit Workspace | reattach the same run-owned Workspace | -| bundled component import | restore the retained `{ kind: "workflow", path, sourceHash, content }` selection and reconstruct the component from that exact source, resolving no name and reading no file | +| bundled component import | restore the saved `{ kind: "workflow", path, sourceHash, content }` selection and reconstruct the component from that exact source, resolving no name and reading no file | | lexical Repository/Worktree | reinstall contextual cwd and live facade | -| Dir directory ensure | restore the recorded `workspace_file` success and retained root without creating again, then reinstall the contextual cwd | +| Dir directory ensure | restore the recorded `workspace_file` success and saved root without creating again, then reinstall the contextual cwd | | Agent provider/session | attach lazily before the first live Agent operation | | Repository base/default resolution | restore pinned result | -| Repository/Worktree creation | restore the retained creation record, then reattach and verify the retained Git state without recloning | +| Repository/Worktree creation | restore the saved creation record, then reattach and verify the saved Git state without recloning | | File read | restore historical content | -| File write/delete | restore completion without mutating again; a deletion whose effect was never published is performed once by the continuation, against the retained frontier | +| File write/delete | restore completion without mutating again; a deletion whose effect was never published is performed once by the continuation, against the saved frontier | | Glob | restore historical path set | -| generated XMD | restore the retained admission — every selected row exact, and nothing about the run's own frontier compared — then offer each nested generated effect inline in authored order: a completed read or mutation restores without another live execution or root publication, an uncommitted one is performed once by the continuation, and the sequence continues to later parent effects and any durable wait | +| generated XMD | restore the saved admission — every selected row exact, and nothing about the run's own frontier compared — then offer each nested generated effect inline in authored order: a completed read or mutation restores without another live execution or root publication, an uncommitted one is performed once by the continuation, and the sequence continues to later parent effects and any durable wait | | Prompt/Sample | restore response | | suspension request | restore its filtered request; with no input, settle the new execution `suspended` and release the executor lock again | -| suspension answer | restore the delivered value without reaching the live controller and without consuming retained delivery state again | +| suspension answer | restore the delivered value without reaching the live controller and without consuming saved delivery state again | | Git.Add/Switch/Commit | restore transactional result | -| Git.Push/PullRequest | restore the retained reconciliation record — including a Push's attested `ancestor` pre-state, which is a fact this run established when it published and never proves again — or observe the Git host again under the same external identity and adopt only a proven compatible completion | -| Issue read | restore the retained `IssueDetails` without reaching `IssueApi` at all, and therefore without network access | -| Issue upsert | restore the retained `IssueReference` without reaching `IssueApi` at all, or call it again under the same idempotency key, which is what lets a provider recognize an issue an interrupted attempt already filed | +| Git.Push/PullRequest | restore the saved reconciliation record — including a Push's attested `ancestor` pre-state, which is a fact this run established when it published and never proves again — or observe the Git host again under the same external identity and adopt only a proven compatible completion | +| Issue read | restore the saved `IssueDetails` without reaching `IssueApi` at all, and therefore without network access | +| Issue upsert | restore the saved `IssueReference` without reaching `IssueApi` at all, or call it again under the same idempotency key, which is what lets a provider recognize an issue an interrupted attempt already filed | Reads restore historical values even when current frontier state differs. Replay never uses a guard such as current file existence to infer whether an @@ -2426,20 +2426,20 @@ Readable is not enough — a valid checkout of an unrelated repository is perfectly readable — so the creation identity the record claims is read back out of the checkout itself: the locator it was cloned from, the algorithm it names objects with, and the presence of the commit it was created at. A Worktree -additionally proves that its checkout is a worktree of the retained Repository +additionally proves that its checkout is a worktree of the saved Repository it belongs to, rather than a repository of its own standing in its place. What attachment deliberately does not require is that HEAD or the current branch equal creation state. Those move, transactionally, when a Git effect moves them; -creation identity does not. Missing, damaged or conflicting retained state is a fatal +creation identity does not. Missing, damaged or conflicting saved state is a fatal stale-input condition: children and later siblings do not begin, `` cannot print it, and nothing is recloned or repaired. -Before replay, a run authenticates its retained component bundle under the +Before replay, a run authenticates its saved component bundle under the executor lock, before beginning a document-execution record, attaching a Workspace or appending anything. -A run authenticates it from its own retained source BLOBs. Every retained length +A run authenticates it from its own saved source BLOBs. Every saved length and content hash is recomputed from the bytes the run holds, and so is the bundle hash over the resulting manifest and mapping. Nothing is read from a repository, from the file the run was started from, or from the files its components were @@ -2448,19 +2448,19 @@ is what the run is a run of. Missing content and content that no longer describes itself are distinct refusals, and neither falls back to any other source. -The obtained bundle then holds the retained history to itself — a +The obtained bundle then holds the saved history to itself — a recorded import naming a component the bundle does not declare, or holding a different path, hash or source, appends nothing and invokes nothing. A completed root result returns without expanding the document or attaching -Workspace, Agent or external providers. It still authenticates the retained -bundle and applies admission, so retained output is accepted only for this run's +Workspace, Agent or external providers. It still authenticates the saved +bundle and applies admission, so saved output is accepted only for this run's history. A partial replay that reaches a completed Git-host effect may still reconstruct what that effect needed locally — a Push rebuilds its checkout from the Workspace in order to name the request it is asking about — and then hands back the -retained record without selecting a provider, contacting the host or appending +saved record without selecting a provider, contacting the host or appending anything. The object-source attachment such a reconstruction produces is never durable and never reaches routing middleware. @@ -2470,25 +2470,25 @@ question about the moment it ran. A run publishes one branch repeatedly, so by the time it is resumed the Workspace has moved on, and reconstructing an earlier Push from the frontier would ask the journal about a request that position never made. The root each completed Push is reconstructed from is therefore the one -its own journal event was appended against, which is the retained association +its own journal event was appended against, which is the saved association the run made when it wrote that event. Nothing else stands in for it: the current pointer and the branch name describe where the run got to, and the -retained Push payload would be answering for itself. +saved Push payload would be answering for itself. Reading that root is a read of history, not a return to it. The historical root is materialized, the checkout family is exported from it, and everything the materialization wrote is discarded on every path out — success, failure and -cancellation alike. The current root, the retained roots, the retained metadata +cancellation alike. The current root, the saved roots, the saved metadata and the journal are exactly what they were, no root is published, and the run's frontier stays where the run left it. What leaves is the exported host tree the reconstruction reads its branch and commit from. -A position with no retained successful Push is a live attempt and names its +A position with no saved successful Push is a live attempt and names its request from the current root, as it always did. The root selected here decides only what question is asked: the request it produces still has to be the one the -retained position asked, so a reconstruction that named the wrong moment refuses +saved position asked, so a reconstruction that named the wrong moment refuses the replay rather than answering it, and it can authorize no Git-host work. Nothing about an advance is -decided a second time: the ancestry a live attempt proved is retained as the +decided a second time: the ancestry a live attempt proved is saved as the relation in its pre-state, so a restored Push builds no control repository, observes no destination and asks no ancestry question — which it could not answer anyway against a remote that has since moved or gone. A live continuation @@ -2540,10 +2540,10 @@ resulting Workspace root commit with the sanitized successful outcome; only then does `` install that directory as cwd for its content. A documented refusal rolls back the savepoint and publishes its sanitized refusal against the unchanged root. An infrastructure failure or cancellation before commit rolls -the outer transaction back and publishes neither outcome nor root. No retained +the outer transaction back and publishes neither outcome nor root. No saved record contains a host path, platform code, raw platform message or cause. -A completed replay restores the recorded success and retained resulting root +A completed replay restores the recorded success and saved resulting root without attempting creation again. Once committed, creation is not owned by the cwd scope: later content failure or cancellation restores the enclosing cwd but does not remove the directory. `` neither reads nor changes the Repository @@ -2558,7 +2558,7 @@ before anything is attempted, including an empty one the pinned filesystem would otherwise remove. It renders nothing and returns nothing. Each deletion is one `workspace_file` effect, named by the expansion, the -operation and the resolved logical target, and its retained outcome is exactly +operation and the resolved logical target, and its saved outcome is exactly `{ kind: "deleted" }` — including the no-op. A deletion an admitted generated fragment performs is that same effect: the standard selection offers the ordinary component (§8.4), so there is one account of the removal and the @@ -2619,7 +2619,7 @@ what the provider looks the effect up by when no local result exists. Credentials are not inputs and stay inside the provider. The durable operation is journaled under the type `git_host_effect` and named by a SHA-256 fingerprint of the complete detached request, so a changed kind, input or natural key -diverges rather than consuming the result retained at the same journal position. +diverges rather than consuming the result saved at the same journal position. There is no schema change, migration or compatibility reader: the filtered Yield is the local record. @@ -2645,7 +2645,7 @@ authorizes a mutation: a later explicit attempt starts again at observation. **The record.** A decision publishes one journal result holding the request, the normalized pre-state, the normalized observations, the decision — `adopted` or `performed` — and the normalized result. Replaying it contacts no provider and -installs none, and the retained record is parsed and compared with the request +installs none, and the saved record is parsed and compared with the request being made before it is accepted. Conflict, permanent ambiguity and temporary unavailability publish the same effect's failed result and replay as the same fixed local failure, rebuilt from its closed name rather than recognized across @@ -2661,7 +2661,7 @@ credential, no capability, no answer operation and no phase evidence. There is one surface rather than two on purpose. A design that selected a provider on one surface and coordinated the invocation on a second let public middleware read a credential from the first, take an invocation capability from -the second, and answer a phase itself — and the journal then retained a +the second, and answer a phase itself — and the journal then saved a completion no provider had produced. Two individually harmless public surfaces composed into the power to settle a completion, so there is now nothing to compose. @@ -2702,15 +2702,15 @@ one place it may prove the two are related. All of it travels as an composition data the trusted host builds for exactly one reconciliation, holds in the selected provider's own closure, and disposes with that invocation. -An attachment is authenticated against the retained identity it belongs to -before it exists — the observed Repository record is compared with the retained -row member for member, every retained row is held to the identity naming it, and +An attachment is authenticated against the saved identity it belongs to +before it exists — the observed Repository record is compared with the saved +row member for member, every saved row is held to the identity naming it, and the exported checkout is proven to be the one that record claims. What it grants access to is authenticated too, and separately: the local state behind an attachment can name further state, so the graph a provider could traverse through it is proven contained before the attachment is first used. That proof is the live provider's first act and happens on no other path — a completed replay -reaches no provider, so it derives and parses its retained record without +reaches no provider, so it derives and parses its saved record without performing it. A graph that leaves what the run authenticated is a boundary failure like any other: it publishes no outcome, exposes no authored path, and activates fail-stop. @@ -2744,7 +2744,7 @@ The durable envelope described here is a workflow run's. Under an ordinary run `` reaches the same configured transport under the same host ceiling with no envelope at all: identity is that execution's own opaque invocation identity together with the engine's expansion identity, an upsert presents an -idempotency key derived from them, and nothing is retained. +idempotency key derived from them, and nothing is saved. An **Issue provider** is an external service that owns a collection of issues. GitHub and Atlassian Cloud are Issue-provider adapters. @@ -2850,7 +2850,7 @@ target. `url` is a credential-free URL naming the container new issues are created in — a GitHub repository issue collection, an Atlassian project. It is canonicalized -before the request is made and retained in canonical form. The context, not the +before the request is made and saved in canonical form. The context, not the component, owns this deployment choice. A nested context replaces the whole target for its descendants; parent and child members are never merged, and leaving the nested context restores the enclosing one whole. @@ -2930,10 +2930,10 @@ could not be asked. whose record carries an operation discriminator so that the two are never confused for one another. A read and an upsert at one position are different questions, and a name that could be either would let a read consume an upsert's -retained result. +saved result. The envelope guarantees that the call happens at most once per position, that a -replay returns the retained result without reaching `IssueApi` at all — and +replay returns the saved result without reaching `IssueApi` at all — and therefore without any network access — and that a changed request diverges rather than consuming another request's result. The complete request fingerprint, which is what the durable operation is named by, covers the @@ -3068,10 +3068,10 @@ positionals, as they do for `start`: they are declared by the document rather than by the command, so option parsing stops at the first one and a positional written after it would not be read. -The new run inherits the source's retained normalized props by default. Explicit +The new run inherits the source's saved normalized props by default. Explicit generated `--props-*` arguments add or override values, property by property, and the merged result is validated against the candidate definition before -anything is created — a property the source retained and the candidate does not +anything is created — a property the source saved and the candidate does not declare is refused here rather than reported later as divergence. Those merged props are what the preflight replays under, what the fork is admitted with, what the run retains, and one of the terms a reused fork ID is compared on. @@ -3085,7 +3085,7 @@ nor `--at`. A fork is a new immutable WorkflowRun identity. It holds a new run ID, the supplied definition entire — its complete component bundle, its manifest and its -retained bytes — the merged normalized props, its lineage, and a journal made of two records it writes for itself followed by +saved bytes — the merged normalized props, its lineage, and a journal made of two records it writes for itself followed by everything it inherited. The two records are its own `workflow_run` and its own root import. The first is @@ -3108,10 +3108,10 @@ so deleting the source after admission leaves the fork's history readable and its roots loadable. When the source is an XMD artifact, lineage additionally retains the XMD -artifact identity. The new run copies every retained record and byte it needs; +artifact identity. The new run copies every saved record and byte it needs; it never attaches the artifact as writable storage or depends on its path after admission. An embedded candidate definition is copied into the new run's -retained definition state for the same reason. +saved definition state for the same reason. ### 11.2 What compatibility means @@ -3142,7 +3142,7 @@ alter the admitted prefix or root. The fork's candidate is established exactly as a `start`'s is: its own document reference, its own bytes, its own logical entrypoint and its own bundle. The -fork is therefore a run of what that file said, and its retained source is +fork is therefore a run of what that file said, and its saved source is independent of the source run's — deleting the source run, the candidate's file or the repository either sat in changes nothing about what the fork replays. Staging assembles the same candidate under the same rules, so a compatibility @@ -3186,7 +3186,7 @@ by the identity the record holds, while every live attempt is named by the run performing it. That is what keeps the fork definitive for what it does next without making it the author of what it inherited. -That identity is established once, when the engine admits the run's retained +That identity is established once, when the engine admits the run's saved snapshot, and published beside the run itself — where every physical copy of the workflow package reads it. A second copy loaded from disk reconciles through the same operation name, and a fork whose Git-host history replayed for one copy and @@ -3195,7 +3195,7 @@ object asked. What makes an answer safe is not where it came from but what happens to a wrong one, and what authorizes live execution is not that answer at all. A run that walked away -from retained history it never consumed performs no Git-host effect at all: it +from saved history it never consumed performs no Git-host effect at all: it asks the Git host nothing and appends nothing. The engine's own account of the coroutine decides that, and it holds for every operation after the one that diverged rather than only at that one — so removing, replacing or forging the @@ -3204,13 +3204,13 @@ replay correctly or refuse. On replay the record consumed is additionally held to the request being made, so an identity that is not that record's own is a refusal too. -It belongs to the exact retained event it came from, at the position that event +It belongs to the exact saved event it came from, at the position that event occupies. One element may ask for more than one Git-host effect, so the records are offered in order: a call may take only the next one not yet taken, and only when that record asks exactly what the call asks. Anything else leaves the inherited prefix behind for good. -Falling through to live is final in the same way. A request named by a retained +Falling through to live is final in the same way. A request named by a saved record that reaches live execution — a mismatch earlier in the history, and a divergence policy that chose to run live, are enough — performs nothing. It asks the Git host nothing, appends nothing, and fails, because the run that may @@ -3229,7 +3229,7 @@ a new session or transcript. ## 12. Retention, deletion and training -Every run status is retained by default. The local host has no automatic +Every run status is saved by default. The local host has no automatic expiration. A deployment may impose an external storage- or age-based policy. Lexical scope cleanup releases live attachments but does not delete run-owned state. @@ -3245,14 +3245,14 @@ Delete targets one authorized run ID, accepts no wildcard and prompts for no additional confirmation. It first acquires the executor lock. A live workflow executor is refused; every status without one may be deleted, including a stale `running` record after acquiring the executor lock proves staleness. The command -removes the local database and run-owned retained provider-session records, and +removes the local database and run-owned saved provider-session records, and reports those categories. An empty advisory-lock sidecar may remain because it -is host arrangement rather than retained run state. An absent run is an error rather +is host arrangement rather than saved run state. An absent run is an error rather than idempotent success. A host pruning operation may select explicit statuses and ages. Documents cannot delete workflow runs. Deletion does not undo pushes, pull requests or other remote effects. Forks and -retained runs keep the Workspace roots they reference; garbage collection may +saved runs keep the Workspace roots they reference; garbage collection may remove an unreferenced historical root only after those references disappear. An exported XMD artifact owns its copied retention independently: deleting its source run does not change or remove the file, and deleting the file is an @@ -3262,7 +3262,7 @@ The journal security policy filters arguments, results, errors and generated XMD before insertion. Retention makes filtered history available for deliberate training ingestion. It does not authorize ingestion of the database's arbitrary filesystem content. Secrets remain ephemeral host-owned bindings and never -belong in retained Workspace configuration. +belong in saved Workspace configuration. ## 13. Local topology @@ -3346,19 +3346,19 @@ Workflow start, resume and the history commands do not reach it in this slice. That boundary holds across processes as well as within one. A host killed between the mutation and the commit publishes nothing, and a process that opens the database afterwards finds the last committed filesystem, current -root, retained roots and ordered event-to-root associations. Reopening replays +root, saved roots and ordered event-to-root associations. Reopening replays recorded effects instead of performing them, and the adapter-private -materializer reconstructs an older event's root from that root's retained DOFS +materializer reconstructs an older event's root from that root's saved DOFS manifests and blobs. Native Git runs against directories, and the authoritative Workspace is a database, so the Deno provider exports a checkout into a disposable host materialization, runs Git there and imports the result back inside the same effect transaction. **Native Git operates only on that export**, and what enforces that is a -distinction between two kinds of retained entry. +distinction between two kinds of saved entry. *Content* is what a commit records. A symbolic link among a checkout's tracked -files is content whatever it points at: it is retained and restored verbatim, +files is content whatever it points at: it is saved and restored verbatim, nothing resolves it, and a target outside the Workspace makes it no less a faithful copy of what Git produced. @@ -3376,22 +3376,22 @@ writes back to what it found. The control plane is therefore validated rather than trusted. A checkout root and a `.git` must be real directories, a linked worktree's `.git` a real regular file, each administration slot and pointer a real entry of its own kind, and -every retained pointer value must name one place beneath the Workspace root — +every saved pointer value must name one place beneath the Workspace root — relative, empty, dot-segmented and traversal-shaped values are refused rather than resolved. The pointers of an exported pair must name each other. All of it is decided before any administration path is rewritten and before any Git command runs, because both are what would otherwise trust it, and any violation -is fatal stale retained state: nothing is repaired, recloned or adopted. Bytes, modes and symbolic-link targets carry both ways. The +is fatal stale saved state: nothing is repaired, recloned or adopted. Bytes, modes and symbolic-link targets carry both ways. The absolute paths Git writes into a linked worktree's administration are reduced to Workspace paths before capture and reconstructed only inside a live -materialization, so nothing retained is specific to the machine that created it. +materialization, so nothing saved is specific to the machine that created it. Deleting a materialization costs time and nothing else. Git runs with a built environment rather than an inherited one — no user configuration, no terminal prompt, and a fixed workflow identity — so a clone does not vary with whoever ran the host. Authentication is the one thing borrowed from the invoking environment, and §13.1 says exactly what is borrowed and when. The narrower retention invariant is about paths: -a provider-generated disposable materialization path is never retained, and -never accepted as a control path. Content is retained as Git produced it, +a provider-generated disposable materialization path is never saved, and +never accepted as a control path. Content is saved as Git produced it, including absolute symbolic-link targets a commit happens to record. SQLite is a host implementation detail. The CLI deliberately exposes no remote @@ -3403,7 +3403,7 @@ delegated without changing the document language. A workflow reaches a private clone or a protected remote wherever the equivalent local Git command reaches one, with no XMD-specific credential setup. That is a property of the machine the run is standing on, not of the run: authentication is -a live host input and never retained workflow state. +a live host input and never saved workflow state. **The credential belongs to one invocation, and to the adapter alone.** The trusted Deno host adapter retains one HTTP credential in memory for one live @@ -3427,7 +3427,7 @@ absent one, and both are authentication unavailability. Transport Git resets ambient credential helpers and installs only the provider-owned one. No ambient `credential.*` definition or value is copied into its arguments, and neither the credential nor the locator nor the marker appears -in a launcher, a command line, a URL or retained configuration. +in a launcher, a command line, a URL or saved configuration. Only what the adapter directly observes is infrastructure: an acquisition process that could not be started, a launcher that could not be installed, a @@ -3473,7 +3473,7 @@ of three host-owned mechanisms: askpass hooks off and `credential.useHttpPath=true` forced. An answer whose protocol, host or path differs from the request has not authorized it, and one that omits the path is an answer about the server rather than the repository. - What comes back is retained by the adapter for this invocation alone. + What comes back is saved by the adapter for this invocation alone. - **GitHub API transport** uses `GH_TOKEN`, then `GITHUB_TOKEN`, then the token `gh auth token --hostname github.com` returns. A variable that is set but empty @@ -3491,7 +3491,7 @@ line. Repository and locator admission checks that operation requires, shared by that invocation's observations and its mutation so both go out under one identity, and disposed with the invocation. A later attempt on an interrupted request opens its -own. The invocation keeps the exact retained locator as its destination. A +own. The invocation keeps the exact saved locator as its destination. A request for another Repository opens a separate exact-locator session; what one session stands on is never carried forward as admission for another. Host helpers remain free to return the same account for several locators according to their @@ -3499,7 +3499,7 @@ own policy. **What still cannot decide anything.** The native Git command still runs in the provider-built environment and, for Push, in the provider-owned control -repository. A retained `.git/config`, caller Git configuration, caller SSH +repository. A saved `.git/config`, caller Git configuration, caller SSH configuration, URL rewrites, hooks, signing programs, filesystem monitors, push URLs and transport programs remain unable to select behavior — `credential.helper` and `core.sshCommand` join that family, fixed on the command line where they @@ -3533,9 +3533,9 @@ fetch operation requires its own language and durability contract. | Contract | Status at this design revision | | --- | --- | | workflow-run and expansion identity | built by #289 / PR #341 | -| retained run record and filtered journal | built by #291 | +| saved run record and filtered journal | built by #291 | | caller-owned storage transaction | built by #291; Workspace mutations join it in #365 | -| provider-backed retained Workspace | document filesystem built by #366 and repository composition by #293; document deletion (§10.1) built by #567 for both providers; mandatory directory ensure is specified by #643 for both providers; process capabilities unbuilt (#218) | +| provider-backed saved Workspace | document filesystem built by #366 and repository composition by #293; document deletion (§10.1) built by #567 for both providers; mandatory directory ensure is specified by #643 for both providers; process capabilities unbuilt (#218) | | `xmd workflow start` / `resume` | built by #366, Deno entrypoints only; both acquire #367's executor lock | | ``, `` and `` composition under a workflow run | Repository and Worktree built by #293, Deno provider only; ``'s mandatory `API.Files.ensureDirectory` is specified by #643 as one transactional `workspace_file` mutation followed by lexical cwd installation | | the same twelve declarations under every runtime | built by #643: one shadowable array declared by the bundled Git Plugin and consumed wherever its profile is assembled — `xmd syntax`, `xmd plan`, an ordinary document execution and a workflow action that executes a document. `` left that array when canonical core took the name, and is present under every runtime without it | @@ -3561,4 +3561,4 @@ fetch operation requires its own language and durability contract. | Deno-local DOFS persistence | POC proven by #349 / PR #350 | | scoped Deno Worker Shell | containment proven by #351 / PR #353 and transactions by #357 / PR #362; production integration unbuilt | | Worker JavaScript | deferred | -| bundled workerd local host | omitted; POC #347 / PR #348 retained as provider evidence | +| bundled workerd local host | omitted; POC #347 / PR #348 saved as provider evidence | diff --git a/specs/xmd-artifact-spec.md b/specs/xmd-artifact-spec.md index 8e0996235..d070d8c96 100644 --- a/specs/xmd-artifact-spec.md +++ b/specs/xmd-artifact-spec.md @@ -30,7 +30,7 @@ the same bytes. Continuing creates a new run with explicit lineage. xmd workflow export run_01J... --output=build-failure.xmd ``` -The command writes one XMD artifact containing the run's committed retained +The command writes one XMD artifact containing the run's committed saved state at one artifact frontier. It reports: ```text @@ -56,7 +56,7 @@ removed before it. A failure, a publication refusal, a cancellation or a cleanup failure up to that moment leaves no file at the requested target and reports failure. After it the artifact is never taken back. One private entry remains, holding the same content under a name nobody was told about; removing it is best -effort, and a failure there is reported beside the success as a retained +effort, and a failure there is reported beside the success as a saved leftover rather than instead of it. Reporting that nothing was published while the complete artifact sits at the requested path would be false, and taking the file back to make it true would destroy the evidence the caller asked for. This @@ -66,7 +66,7 @@ named. ### 1.2 Inspect the file anywhere -The existing inspection shapes accept either one retained run ID or one +The existing inspection shapes accept either one saved run ID or one explicit artifact path: ```sh @@ -78,14 +78,14 @@ xmd workflow history --artifact=build-failure.xmd --json ``` The positional run ID and `--artifact` are mutually exclusive. `list` remains a -list of live retained runs and accepts no artifact: a directory of downloaded +list of live saved runs and accepts no artifact: a directory of downloaded files is not a run registry. -Artifact status and history expose the same semantic fields as their retained-run +Artifact status and history expose the same semantic fields as their saved-run forms, plus the artifact identity and artifact frontier. Human presentation may name the artifact path. JSON never makes that host path part of identity. -Artifact status JSON is the retained-run snapshot with one added member: +Artifact status JSON is the saved-run snapshot with one added member: ```jsonc { @@ -100,23 +100,23 @@ Artifact status JSON is the retained-run snapshot with one added member: `retrieval` is absent, and absent as a fact: an artifact excludes where its definition could be fetched from now, because that is ownership belonging to the machine that exported rather than something true about the run. The shared -inspection result declares the members both forms have, the retained-run result +inspection result declares the members both forms have, the saved-run result alone declares `retrieval`, and the artifact result declares the artifact metadata — so an artifact snapshot has no such member to carry, rather than a member one projection happens to leave unset. -Artifact history JSON is an envelope rather than the retained-run array, because +Artifact history JSON is an envelope rather than the saved-run array, because the identity and the frontier have nowhere else to go — repeating them on every row would make one fact into many: ```jsonc { "artifact": { "identity": "…", "frontier": { /* … */ } }, - "entries": [ /* exactly the retained-run history rows */ ] + "entries": [ /* exactly the saved-run history rows */ ] } ``` -Retained-run history JSON remains the bare array. Both forms always carry +Saved-run history JSON remains the bare array. Both forms always carry forkability, so `--forkable` adds two human columns and changes no JSON value. Inspection opens the container read-only, executes no document, materializes no @@ -151,7 +151,7 @@ copy into a destination run without making the artifact path retrieval state. For an artifact source, the definition argument is optional. When absent, the candidate definition is the artifact's workflow definition source. The new run -copies it into its retained definition state, so later resume does not depend on +copies it into its saved definition state, so later resume does not depend on the artifact path or on access to the original repository. When present, the candidate definition is resolved normally and the source run's normalized props remain the baseline under the workflow history-fork contract. @@ -214,17 +214,17 @@ The frontier consists of one mutually consistent view of: lineage; - lifecycle status, stop reason and document-execution records; - the complete filtered journal through its last committed event; -- every retained Workspace root and the current root association; and -- every other XMD-owned retained record referenced by that state. +- every saved Workspace root and the current root association; and +- every other XMD-owned saved record referenced by that state. The status may be `running` when the prior host died without settling. Export does not repair or relabel it. If SQLite recovery is necessary, the provider copies the database and rollback journal under inspection recovery coordination -and recovers only that private copy. The retained source remains unchanged. +and recovers only that private copy. The saved source remains unchanged. Export performs no lifecycle transition, stale-execution settlement, replay, external reconciliation or document execution. Releasing the executor lock -after export changes no retained state. +after export changes no saved state. ### 2.3 Complete XMD-owned state @@ -235,17 +235,17 @@ frontier and attempt a compatible history fork: - the artifact frontier and lifecycle snapshot; - the complete already-filtered journal, including event IDs, authored source, Workspace-root associations and inherited-event provenance; -- every Workspace root, manifest, retained file byte, mode, symbolic-link +- every Workspace root, manifest, saved file byte, mode, symbolic-link target, hardlink relationship and Repository/Worktree record the run retains; - suspension requests, delivered answers and answer-consumption state; - completed and interrupted external-effect requests, identities and filtered - outcomes already retained by the workflow contract; -- workflow Agent session mappings and compatibility attributes already retained + outcomes already saved by the workflow contract; +- workflow Agent session mappings and compatibility attributes already saved in the run database; - in a finalized artifact, one portability classification for every logical - Agent session that contributed a retained Prompt, and the opaque bundle bytes + Agent session that contributed a saved Prompt, and the opaque bundle bytes of each portable one (§2.5); and -- the exact retained workflow definition source closure (§2.4). +- the exact saved workflow definition source closure (§2.4). “Complete” is bounded by XMD ownership. The artifact does not claim to capture the state of the world around the run. @@ -258,7 +258,7 @@ expanded. Export reads only the run's own source store. It reaches no repository and needs none, so a run exports after the file it was started from has been edited, moved -or deleted. Every retained length and content hash is recomputed from the bytes +or deleted. Every saved length and content hash is recomputed from the bytes the run holds, and so is the bundle hash over the resulting manifest and mapping; missing content and content that no longer describes itself are distinct refusals, and neither falls back to any other source. @@ -274,12 +274,12 @@ Version 1 has two profiles, and a reader recognizes which one it is holding only after every container, manifest, content and artifact-identity gate has passed. **Merged legacy V1** holds neither Agent content kind. Its sessions may have -contributed retained Prompts and carry no classification at all, and the reader +contributed saved Prompts and carry no classification at all, and the reader preserves that absence: no record, token, bundle or marker is reconstructed for it. **Finalized Agent-aware V1** classifies every logical Agent session that -contributed a retained Prompt, exactly once, as one of: +contributed a saved Prompt, exactly once, as one of: - **portable** — the logical session key, session identity, provider, agent command and policy, the bundle kind and compatibility identifier, the source @@ -287,29 +287,29 @@ contributed a retained Prompt, exactly once, as one of: ordered Prompt-event and checkpoint-token associations, and the bundle's byte length and lowercase SHA-256, beside the opaque bundle bytes themselves; or - **unavailable** — the same logical session and associations, and one reason. - `checkpoint-token-unavailable` means at least one retained Prompt did not - complete or has no retained association; it wins wherever it applies, because - missing retained evidence is intrinsic and no other host's capability can - repair it. `provider-capability-unavailable` means every retained Prompt + `checkpoint-token-unavailable` means at least one saved Prompt did not + complete or has no saved association; it wins wherever it applies, because + missing saved evidence is intrinsic and no other host's capability can + repair it. `provider-capability-unavailable` means every saved Prompt completed and the associations cover them exactly, and export had no declared compatible capture capability. -The repeated session members must equal the retained Agent session mapping, and +The repeated session members must equal the saved Agent session mapping, and a portable record's source provider session must equal that mapping's canonical -assertion. The bundled identity is retained separately, because detached bytes +assertion. The bundled identity is saved separately, because detached bytes may name a transported session rather than the live one they came from. -Associations are in journal append order and each names one distinct retained +Associations are in journal append order and each names one distinct saved `agent_prompt` whose Prompt record completed for that exact session. A portable -record covers every retained Prompt of its session and has exactly one bundle +record covers every saved Prompt of its session and has exactly one bundle under the same key, whose bytes match the declared length and hash. An unavailable record has none. -A session that retained no Prompt has no classification and no bundle bytes. An +A session that saved no Prompt has no classification and no bundle bytes. An artifact holding no Agent Prompt and neither Agent kind is valid, and legacy and finalized writers are deliberately indistinguishable for that inventory. -A checkpoint token is opaque retained evidence about where a conversation +A checkpoint token is opaque saved evidence about where a conversation reached, not control over the host that issued it, and is never derived from event position, transcript text, another token or a provider head. A bundle is opaque bytes that nothing scans or scrubs, and §3.1 applies to it in full. @@ -323,7 +323,7 @@ corruption, and yields no projection. The artifact excludes: - credentials, authentication tokens, authentication helper output and - environment bindings — a provider checkpoint token retained under §2.5 is + environment bindings — a provider checkpoint token saved under §2.5 is evidence about a conversation rather than a way to reach a host, and is not one of these; - retrieval metadata and provider endpoints; @@ -348,7 +348,7 @@ inside the Workspace. An XMD artifact may therefore contain source code, generated files or secrets that the workflow wrote into its Workspace. Export is a deliberate disclosure boundary. Operators treat the artifact with -the confidentiality of the complete retained Workspace, not merely the +the confidentiality of the complete saved Workspace, not merely the rendered log. The command states this in help and CI integrations do not publish an artifact more broadly than the source run's Workspace may be published. @@ -368,7 +368,7 @@ session. Intrinsic forkability does not read it: an Agent turn is still `agent-state-unavailable`, and whether a destination can consume a particular bundle is decided by artifact-backed fork rather than by inspection. -Completed external effects replay from their retained records and do not contact +Completed external effects replay from their saved records and do not contact their providers. An interrupted or otherwise unsettled external effect is forkable only when the existing reconciliation contract establishes a safe continuation. A checkpoint that cannot establish it is @@ -385,7 +385,7 @@ before any discoverable new run exists. ### 4.1 Canonical artifact manifest Every artifact carries one versioned artifact manifest. It canonically -enumerates all semantic records and retained bytes in the artifact, including +enumerates all semantic records and saved bytes in the artifact, including their kinds, logical identities, lengths and content hashes. Ordering and value encoding are part of the manifest version. Physical SQLite page order, free space, indexes and host path are not. @@ -450,7 +450,7 @@ re-encoded. Content with no text meaning is `bytes`. #### 4.1.2 The version 1 inventory is closed -Version 1 enumerates every XMD-owned semantic record and retained byte in these +Version 1 enumerates every XMD-owned semantic record and saved byte in these groups, and admits nothing else: - the artifact frontier: the source run id, the final committed event id when @@ -458,23 +458,23 @@ groups, and admits nothing else: - the workflow run record as `workflow-run`, carrying the storage definition and the `WorkflowRun`, without definition retrieval metadata, a `base` or a pinned commit; -- every document-execution record, in its retained order; +- every document-execution record, in its saved order; - the prior workflow-fork lineage, when the source run was itself a fork; - every journal row in append order: its event id, the exact filtered protocol record, its Workspace-root association, and its inherited provenance when it has one. The authored source position lives inside the record and is - validated from it rather than projected beside it, and so does every retained + validated from it rather than projected beside it, and so does every saved external-effect request, identity and filtered result; -- every retained Workspace root, its exact canonical manifest bytes, and its +- every saved Workspace root, its exact canonical manifest bytes, and its declared root-to-manifest and root-to-blob references; -- every retained DOFS manifest record and its exact encoded bytes, and every - retained blob record and its exact bytes, that those roots require; +- every saved DOFS manifest record and its exact encoded bytes, and every + saved blob record and its exact bytes, that those roots require; - every Workspace Repository and Worktree creation record, carrying the credential-free locator and the logical Workspace checkout path — never retrieval metadata or a host checkout path; - every suspension-answer record, with its request identity, its pending or consumed state, and its timestamps; -- every retained workflow Agent session mapping and its compatibility +- every saved workflow Agent session mapping and its compatibility attributes, without any provider-owned conversation store or session directory; - in a finalized artifact, one `agent-session-portability` record as @@ -483,7 +483,7 @@ groups, and admits nothing else: key for every portable one (§2.5). Both take part in the manifest and the artifact identity; neither is a profile marker, and no other Agent portability kind exists; and -- the workflow definition source closure, one pair per retained source: +- the workflow definition source closure, one pair per saved source: - `definition-source-entry` is `canonical-json` under the canonical JSON string of the source's logical path, containing exactly the members `path`, @@ -555,7 +555,7 @@ An XMD reader validates, in order: 4. exact structural recognition with no undeclared schema objects; 5. the artifact manifest and XMD artifact identity; and 6. the workflow, journal, Workspace and definition invariants used for a live - retained snapshot. + saved snapshot. No status, history row or Workspace byte is returned before complete recognition succeeds. A foreign SQLite database, live run database, newer @@ -580,15 +580,15 @@ Export refuses without producing the target when: - another workflow executor holds its lock; - a consistent committed frontier cannot be read; - the workflow definition source cannot be obtained and authenticated: the - retained manifest or content is missing, or disagrees with the descriptor; -- any retained state required by the artifact manifest is unreadable; + saved manifest or content is missing, or disagrees with the descriptor; +- any saved state required by the artifact manifest is unreadable; - the target exists, lacks the `.xmd` extension or cannot be published atomically; or - provider-private temporary cleanup fails before publication. Publication is the commit point. Cleanup of the one private entry that remains after it cannot invalidate or roll back the committed artifact: it is reported -as a retained leftover beside a successful export. +as a saved leftover beside a successful export. Inspection refuses without a partial result when recognition, manifest verification or §2.5 profile recognition fails. Profile recognition runs after @@ -605,7 +605,7 @@ Fork refuses before creating discoverable run storage when: at the selected frontier. Diagnostics name the artifact path, categorical condition and non-secret -identities needed to act. They do not quote retained file bytes, journal values, +identities needed to act. They do not quote saved file bytes, journal values, credentials or retrieval metadata. ## 7. Structural acceptance checklist @@ -616,7 +616,7 @@ Architecture review freezes these invariants before implementation: run ID. 2. Export chooses one committed frontier while holding the source executor lock and leaves the source unchanged. -3. The artifact contains the complete retained XMD state and authenticated +3. The artifact contains the complete saved XMD state and authenticated workflow definition source closure required by this specification. 4. Every reader validates the whole artifact manifest before returning a partial answer and never mutates or migrates the file. @@ -639,7 +639,7 @@ Architecture review freezes these invariants before implementation: | Contract | Status at this design revision | | --- | --- | | XMD artifact terminology and structural boundary | specified in `architecture.md`; built | -| `xmd workflow export` | specified; built, Deno provider only. A run exports from its own retained source, so export reaches no repository | +| `xmd workflow export` | specified; built, Deno provider only. A run exports from its own saved source, so export reaches no repository | | the artifact format | specified; the manifest version, identity domain, closed inventory, source entry/content pairs and semantic verifier are built, Deno provider only | | artifact status/history and manifest verification | specified; built, Deno provider only. `inspectArtifact()` and `historyArtifact()` are sibling lifecycle operations, and history answers with an envelope | | version 1 Agent session portability evidence | specified; the two content kinds, the closed union and the complete post-identity profile verifier are built. Production export still emits merged-legacy V1, no provider bundle capture exists, and inspection carries none of it | diff --git a/test-weights.json b/test-weights.json index ac72fde2d..1871a4e70 100644 --- a/test-weights.json +++ b/test-weights.json @@ -259,7 +259,7 @@ "packages/durable-streams/tests/parse.test.ts": 1253, "packages/durable-streams/tests/replay-guard.test.ts": 1314, "packages/durable-streams/tests/replay-index.test.ts": 1219, - "packages/durable-streams/tests/retained.test.ts": 1130, + "packages/durable-streams/tests/saved.test.ts": 1130, "packages/durable-streams/tests/serialize-event.test.ts": 1294, "packages/durable-streams/tests/smoke.test.ts": 1215, "packages/durable-streams/tests/structured-concurrency.test.ts": 1334, @@ -367,7 +367,7 @@ "packages/workflow/tests/generated-agent-component.test.ts": 5082, "packages/workflow/tests/generated-observations.test.ts": 2787, "packages/workflow/tests/public-entrypoint.test.ts": 4054, - "packages/workflow/tests/retained-run.test.ts": 3428, + "packages/workflow/tests/saved-run.test.ts": 3428, "packages/workflow/tests/service-denial.test.ts": 1240, "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 4268, "packages/workflow/tests/workflow-agent-sessions.test.ts": 3376, @@ -671,7 +671,7 @@ "packages/durable-streams/tests/parse.test.ts": 395, "packages/durable-streams/tests/replay-guard.test.ts": 402, "packages/durable-streams/tests/replay-index.test.ts": 357, - "packages/durable-streams/tests/retained.test.ts": 370, + "packages/durable-streams/tests/saved.test.ts": 370, "packages/durable-streams/tests/serialize-event.test.ts": 362, "packages/durable-streams/tests/smoke.test.ts": 354, "packages/durable-streams/tests/structured-concurrency.test.ts": 403, @@ -745,7 +745,7 @@ "packages/web/tests/server-lifecycle.test.ts": 1027, "packages/web/tests/server.test.ts": 1322, "packages/workflow/tests/generated-observations.test.ts": 925, - "packages/workflow/tests/retained-run.test.ts": 1408, + "packages/workflow/tests/saved-run.test.ts": 1408, "packages/workflow/tests/service-denial.test.ts": 342, "packages/workflow/tests/workflow-bundle.test.ts": 1062, "packages/workflow/tests/workflow-definition.test.ts": 856, @@ -1011,7 +1011,7 @@ "packages/durable-streams/tests/parse.test.ts": 33, "packages/durable-streams/tests/replay-guard.test.ts": 45, "packages/durable-streams/tests/replay-index.test.ts": 31, - "packages/durable-streams/tests/retained.test.ts": 44, + "packages/durable-streams/tests/saved.test.ts": 44, "packages/durable-streams/tests/serialize-event.test.ts": 47, "packages/durable-streams/tests/smoke.test.ts": 21, "packages/durable-streams/tests/structured-concurrency.test.ts": 60, @@ -1085,7 +1085,7 @@ "packages/web/tests/server-lifecycle.test.ts": 448, "packages/web/tests/server.test.ts": 715, "packages/workflow/tests/generated-observations.test.ts": 376, - "packages/workflow/tests/retained-run.test.ts": 870, + "packages/workflow/tests/saved-run.test.ts": 870, "packages/workflow/tests/service-denial.test.ts": 53, "packages/workflow/tests/workflow-bundle.test.ts": 506, "packages/workflow/tests/workflow-definition.test.ts": 252, diff --git a/tsconfig.node.json b/tsconfig.node.json index 44331d6ca..80d2179d3 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -133,6 +133,6 @@ // this proof observes `Deno.openSync` directly and spawns a second // `Deno.Command` to take the same sidecar from another process. The runtime // exclusions keep the same test out of the Node and Bun execution suites. - "packages/git/tests/retained-repository-lifecycle.test.ts" + "packages/git/tests/saved-repository-lifecycle.test.ts" ] }