Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 100 additions & 93 deletions architecture.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/cli/src/evaluation-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
*
* `<Fetch>` is absent rather than present-and-bounded. An unbounded network
* read is a decision this command does not make on a document's behalf, and
* admitting the identity with an empty ceiling would be a different thing from
* not admitting it β€” the fragment would name a component it can never
* successfully call.
* admitting the name with an empty ceiling would be a different thing from not
* admitting it β€” the fragment would name a component it can never successfully
* call.
*
* ## No Workspace, and no alias
* ## No Workspace
*
* An ordinary run evaluates against no Workspace. Its admitted effects address
* the Files provider its own execution installed, and there is no immutable
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,8 +1076,9 @@ export function runWorkflow(
// about the component. Stated where the Workspace is attached β€” a
// completed replay restores its retained output and expands nothing, so
// it needs no ceiling of its own.
// `<Dir>`'s entry comes from the package that owns the component, so
// the write table names one identity rather than two copies of it.
// `<Dir>`'s entry comes from the Git Plugin, which decides that a
// workflow run has one at all; the identity it is admitted under is
// core's, from the declaration that owns the component.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment β€” restates what the code does.

Suggested change
// core's, from the declaration that owns the component.

...(completed || replay
? []
: [
Expand Down
18 changes: 7 additions & 11 deletions packages/cli/tests/evaluate-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,15 @@ describe("Tier FE β€” the workflow host's profile", () => {
});
});

it("FE23: Git's directory entry is the exact identity retained history holds", function* () {
// Written out here rather than compared against the implementation, so a
// change to either side is a change to this row. Every released journal
// holds these strings: the origin a released build recorded, the revision
// it recorded, and the version-1 alias whose grant this entry answers for.
it("FE23: Git's directory entry admits core's own Dir and states no identity", function* () {
// What this host decides is that a workflow run has a `<Dir>` at all. What
// admitting it grants is core's: the identity and the effect its paired
// spelling runs under are on the declaration that owns the component, so
// there is nothing here for a host to restate or to move.
const entry = gitDirectoryEntry();
expect(entry.name).toBe("Dir");
expect(entry.identity).toEqual({
origin: "@executablemd/workflow/composition",
key: "Dir",
revision: "3",
});
expect(Reflect.get(entry, "legacy")).toEqual(["@executablemd/workflow/composition/dir-v2#Dir"]);
expect(Object.keys(entry)).not.toContain("identity");
expect(Object.keys(entry)).not.toContain("legacy");
// It is the directory capability, not some other entry wearing the name.
expect(Reflect.get(entry, "capability")).toBe("directory:ensure");
});
Expand Down
16 changes: 9 additions & 7 deletions packages/cli/tests/plan-command-document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1184,12 +1184,7 @@ describe("Tier PI β€” read-only information requests", () => {

/** The admitted entry the barrier is written as. */
function heldEntry(): FragmentEntry {
return {
kind: "component-answer",
name: "Held",
identity: { origin: HELD_ORIGIN, key: "Held", revision: "1" },
forms: ["self-closing"],
};
return { kind: "component-answer", name: "Held", forms: ["self-closing"] };
}

/** One barrier: a body whose cleanup blocks until the case releases it. */
Expand All @@ -1209,6 +1204,13 @@ describe("Tier PI β€” read-only information requests", () => {
kind: "function",
name: "Held",
props: { type: "object", properties: {}, additionalProperties: false },
forms: ["self-closing"],
generated: {
"self-closing": {
identity: "https://executable.md/components/held/read",
effect: "read",
},
},
*fn(): Operation<Json> {
// Registered before anything can fail, so the barrier is reached
// whichever way this projection ends.
Expand All @@ -1219,7 +1221,7 @@ describe("Tier PI β€” read-only information requests", () => {
return "held";
},
},
{ origin: HELD_ORIGIN, key: "Held", revision: "1" },
{ origin: HELD_ORIGIN },
),
};
}
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/tests/plan-component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2027,17 +2027,16 @@ describe("Tier FE β€” Plan produces text, Evaluate runs it", () => {
* The profile these rows evaluate under: the read entry, and never the wide
* one.
*
* The entry's identity is the fixture's own, so the catalog an agent is shown
* inside an evaluation names `test://fe713/read` and the admitted capability
* is core's file read behind it.
* The entry's name is the fixture's own, so the catalog an agent is shown
* inside an evaluation names `<FE713.Read>` and the admitted capability is
* core's file read behind it.
*/
function fixtureProfile(): FragmentEvaluationInput {
return {
read: [
{
...fileReadEntry(),
name: READ_COMPONENT.name,
identity: { origin: READ_COMPONENT.origin, key: "FE713.Read", revision: "1" },
description: READ_COMPONENT.description,
},
],
Expand Down Expand Up @@ -2163,7 +2162,10 @@ describe("Tier FE β€” Plan produces text, Evaluate runs it", () => {
expect(seen).toHaveLength(1);
const catalog = seen[0] ?? "";
expect(catalog).toContain(READ_MARKER);
expect(catalog).toContain(READ_COMPONENT.origin);
// Reported as the implementation it runs rather than as the name it was
// admitted under: the entry is core's own file read, which is what the
// host chose to admit under a name of its own.
expect(catalog).toContain("@executablemd/core");
// The wide-only component exists in this scope and is not in the profile,
// so its absence here is the narrowing rather than the fixture.
expect(catalog).not.toContain(WIDE_MARKER);
Expand Down
19 changes: 12 additions & 7 deletions packages/cli/tests/workflow-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,16 +722,21 @@ describe("Tier WAL β€” the workflow Agent observation loop", () => {
});

expect(attempt.failure).toBe(undefined);
// The standard profile admitted it: one record, naming both write
// identities and the classes the document selected.
// The standard profile admitted it: one record, naming both admitted
// spellings and the classes the document selected.
const recorded = admitted(attempt.events);
expect(recorded).toHaveLength(1);
const policy = JSON.stringify(recorded[0]);
expect(policy).toContain("File:write");
// The identity is a structural record, so the origin, the key and the
// revision are read as themselves rather than as one assembled spelling.
expect(policy).toContain('"origin":"@executablemd/workflow/composition"');
expect(policy).toContain('"key":"Dir","revision":"3"');
// What each spelling runs as comes from the declaration that owns the
// implementation, so the row holds that URL and the table that admitted
// it rather than a spelling a host assembled.
expect(policy).toContain(
'"name":"File","form":"paired","identity":"https://executable.md/components/file/write"',
);
expect(policy).toContain(
'"name":"Dir","form":"paired","identity":"https://executable.md/components/dir/ensure"',
);
expect(policy).toContain('"effect":"write"');
expect(policy).toContain('"allow":["write"]');
// And the change is in the run's own Workspace, where an ordinary read
// beneath the fragment's own directory finds it. Anchored on the
Expand Down
55 changes: 32 additions & 23 deletions packages/cli/tests/workflow-suspension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,15 @@ const PROMPT_EFFECT = "agent_prompt"; // packages/core/src/agent/journal.ts
const EVALUATION_EFFECT = "generated_xmd"; // packages/core/src/generated-xmd.ts
const FILE_EFFECT = WORKSPACE_FILE;

/**
* What core's own declaration offers the paired `<Dir>` as.
*
* Spelled here rather than imported, because a case about what a retained
* 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.
*/
const DIR_ENSURE = "https://executable.md/components/dir/ensure";

/** Every prompt this run's agent was actually asked. */
interface AgentCalls {
readonly prompts: string[];
Expand Down Expand Up @@ -1508,10 +1517,10 @@ the retained note
});
}

// WGAC17: `allow={["write"]}` admits the versioned paired `<Dir>`, and that
// entry now authorizes persistent recursive creation. The former identity
// authorized placement that created nothing, so a run admitted under it must
// not silently receive the wider permission.
// WGAC17: `allow={["write"]}` admits the paired `<Dir>`, which authorizes
// persistent recursive creation. A run admitted under a different identity
// for that spelling was granted something else, so it must not silently
// receive this permission on resume.
//
// Two halves, and neither is evidence alone. The control shows the current
// admission really does execute its generated `<Dir>` and really does resume
Expand Down Expand Up @@ -1541,12 +1550,14 @@ the retained note

const suspended = retained(path);
expect(suspended.status).toBe("suspended");
// The admission carries the versioned identity. It is a structural record
// rather than a spelling, and the version is the `revision` member β€” so
// that is what this reads, and what the refusal below moves.
// 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
// this reads, and what the refusal below moves.
expect(
readRecords(path).some(
(record) => record.includes(`"key":"Dir"`) && record.includes(`"revision":"3"`),
(record) =>
record.includes(`"name":"Dir","form":"paired","identity":"${DIR_ENSURE}"`) &&
record.includes(`"effect":"write"`),
),
).toBe(true);

Expand Down Expand Up @@ -1616,19 +1627,18 @@ the retained note
expect(counts(path)).toEqual(atSuspension);
});

it("WGAC17: a continuation retaining the former Dir identity refuses before generated execution and mutation", function* () {
it("WGAC17: a continuation retaining another Dir identity refuses before generated execution and mutation", function* () {
const store = yield* useRunStore();
const { runId, path, rendered, calls } = yield* wgac17(store);

// The run becomes one admitted under the former identity. Nothing else
// changes β€” same source, roots and selection β€” so the identity is the only
// thing the resume can disagree about.
//
// The version is the `revision` member, and core's own entries state `2`,
// so `"revision":"3"` names the Dir entry and nothing else. Moving it back
// is exactly "admitted under the earlier revision", which is the grant the
// former placement-only Dir had.
rewriteRecords(path, (record) => record.replaceAll(`"revision":"3"`, `"revision":"2"`));
// The run becomes one admitted under a different identity for the same
// spelling of the same name. Nothing else changes β€” same source, roots and
// selection β€” so the identity is the only thing the resume can disagree
// about, and it is still a URL this engine can read: the refusal is the
// grant comparison rather than an unreadable record.
rewriteRecords(path, (record) =>
record.replaceAll(DIR_ENSURE, "https://executable.md/components/dir/placement"),
);
const beforeResume = counts(path);
const rootsBeforeResume = workspaceRootState(path);

Expand All @@ -1646,11 +1656,10 @@ the retained note
expect(workspaceRootState(path)).toEqual(rootsBeforeResume);
// Nothing after the wait reached the document either.
expect(rendered).toEqual([]);
// And the refusal does not publish which identity moved. The identity is
// four members now, so this reads the two a refusal could carry: the origin
// that owns the implementation, and the member the rewrite actually moved.
// And the refusal does not publish which identity moved: neither the one
// the admission holds nor the one the rewrite put there.
const reported = resumed.written.err.join("\n");
expect(reported).not.toContain("@executablemd/workflow/composition");
expect(reported).not.toContain("revision");
expect(reported).not.toContain(DIR_ENSURE);
expect(reported).not.toContain("/components/dir/placement");
});
});
3 changes: 1 addition & 2 deletions packages/core/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export type {
FragmentEntry,
FragmentEvaluationInput,
FragmentForm,
FragmentIdentity,
FragmentWorkspaceAccess,
} from "./src/evaluation-profile.ts";
/**
Expand Down Expand Up @@ -329,11 +328,11 @@ export {
export type {
GeneratedComponentForm,
GeneratedEffectClass,
GeneratedImplementation,
GeneratedMutation,
GeneratedObservation,
GeneratedRequest,
GeneratedXmdRequest,
RetainedFragmentIdentity,
} from "./src/generated-xmd.ts";

/**
Expand Down
7 changes: 7 additions & 0 deletions packages/core/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@ export {
registerComponents,
} from "./src/components/registration.ts";
export type { ComponentRegistration } from "./src/components/registration.ts";
// What a registration says about the generated use of its own forms. The two
// members a declaration states; nothing here reads or publishes a catalog of them.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment β€” restates what the code does.

Suggested change
// members a declaration states; nothing here reads or publishes a catalog of them.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment β€” restates what the code does.

Suggested change
// members a declaration states; nothing here reads or publishes a catalog of them.

export type {
GeneratedEffect,
GeneratedForm,
GeneratedForms,
} from "./src/components/generated-metadata.ts";
export { DEFAULT_INCLUDES, selectComponent } from "./src/components/select.ts";
export type { SelectOptions } from "./src/components/select.ts";
export { CORE_COMPONENT_NAMES } from "./src/components/registry.ts";
Expand Down
39 changes: 23 additions & 16 deletions packages/core/src/component-answers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,44 @@
*
* A `component-answer` entry names something the ordinary import chain
* resolves rather than an operation core supplies a body for. The host does
* not hand over the implementation β€” it states which name, and which
* structural identity a provider must have stated for that name. This module
* is the seam a provider uses to state that identity.
* not hand over the implementation β€” it states which name, and which spellings
* of it the entry admits. This module is the seam a provider publishes the
* definition through, and what each of that definition's spellings runs as is
* declared on the definition itself
* (`components/generated-metadata.ts`).
*
* ## Being asked and answering are two separate rights
*
* A provider installs through a *registrar*, and receives no handle it can keep
* and state an answer with. The registrar is the right to be asked; each
* and publish an answer with. The registrar is the right to be asked; each
* handler invocation then receives its own {@link ComponentAnswerRequest},
* which is the right to answer *that* asking and nothing else.
*
* An installation is reusable, but cannot identify which invocation is
* speaking. Each request captures its resolution by object identity and closes
* when its handler returns, throws or is cancelled, so a finished or losing
* handler cannot state an answer to an import it is no longer deciding.
* handler cannot publish an answer to an import it is no longer deciding.
*
* So `claim` takes no name. The name is fixed when the request is minted, from
* what the chain asked, and a handler cannot state an answer for a component it
* was never asked about.
* So `answer` takes the definition and nothing else. The name is fixed when the
* request is minted, from what the chain asked, so a handler cannot answer for
* a component it was never asked about; and the identity and effect of each
* spelling are the definition's own, so a provider cannot describe an
* implementation it did not supply. The installation origin it carries is
* provenance a reader and a documentation symbol are shown, and no part of what
* the implementation is.
*
* All of it is ordinary closures. There is no shared symbol, module registry or
* context name behind any of these objects, which is what lets a separately
* loaded copy of core hold one without any of those being a way in.
*
* ## Identifying is not permission
* ## Publishing is not permission
*
* An identity statement permits nothing. A fragment still needs the host entry,
* an exact identity match, the right `allow` class, whole-fragment preflight
* and canonical generated import β€” all of them independently. Middleware that
* answers an import without stating an identity is an ordinary replacement
* and stays exactly as valid as it was; it cannot enter an admitted fragment.
* A publication permits nothing. A fragment still needs the host entry, the
* spelling the definition's own declaration offers, the right `allow` class,
* whole-fragment preflight and canonical generated import β€” all of them
* independently. Middleware that answers an import without publishing it is an
* ordinary replacement and stays exactly as valid as it was; it cannot enter an
* admitted fragment.
*/

import type { Operation } from "effection";
Expand Down Expand Up @@ -85,8 +92,8 @@ export interface ComponentAnswerRegistrar {
/**
* One provider's installation, run during capture and before ordinary installs.
*
* `origin` is the provider's name for itself and becomes the origin on every
* identity its requests state. `install` registers whatever import middleware
* `origin` is the provider's name for itself, and the provenance every answer
* its requests publish carries. `install` registers whatever import middleware
* answers for its names.
*/
export interface ComponentAnswerInstallation {
Expand Down
Loading
Loading