Skip to content

Commit 3a1fade

Browse files
authored
Merge pull request #919 from taras/agent/upstream-u4-generated-owners
💥 Let a component say what its own spellings are offered as (#181)
2 parents b5a2b3c + 4c05e45 commit 3a1fade

48 files changed

Lines changed: 4155 additions & 3004 deletions

Some content is hidden

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

‎architecture.md‎

Lines changed: 100 additions & 93 deletions
Large diffs are not rendered by default.

‎packages/cli/src/evaluation-profile.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@
3838
*
3939
* `<Fetch>` is absent rather than present-and-bounded. An unbounded network
4040
* read is a decision this command does not make on a document's behalf, and
41-
* admitting the identity with an empty ceiling would be a different thing from
42-
* not admitting it — the fragment would name a component it can never
43-
* successfully call.
41+
* admitting the name with an empty ceiling would be a different thing from not
42+
* admitting it — the fragment would name a component it can never successfully
43+
* call.
4444
*
45-
* ## No Workspace, and no alias
45+
* ## No Workspace
4646
*
4747
* An ordinary run evaluates against no Workspace. Its admitted effects address
4848
* the Files provider its own execution installed, and there is no immutable

‎packages/cli/src/workflow.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,8 +1076,9 @@ export function runWorkflow(
10761076
// about the component. Stated where the Workspace is attached — a
10771077
// completed replay restores its retained output and expands nothing, so
10781078
// it needs no ceiling of its own.
1079-
// `<Dir>`'s entry comes from the package that owns the component, so
1080-
// the write table names one identity rather than two copies of it.
1079+
// `<Dir>`'s entry comes from the Git Plugin, which decides that a
1080+
// workflow run has one at all; the identity it is admitted under is
1081+
// core's, from the declaration that owns the component.
10811082
...(completed || replay
10821083
? []
10831084
: [

‎packages/cli/tests/evaluate-workflow.test.ts‎

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,15 @@ describe("Tier FE — the workflow host's profile", () => {
106106
});
107107
});
108108

109-
it("FE23: Git's directory entry is the exact identity retained history holds", function* () {
110-
// Written out here rather than compared against the implementation, so a
111-
// change to either side is a change to this row. Every released journal
112-
// holds these strings: the origin a released build recorded, the revision
113-
// it recorded, and the version-1 alias whose grant this entry answers for.
109+
it("FE23: Git's directory entry admits core's own Dir and states no identity", function* () {
110+
// What this host decides is that a workflow run has a `<Dir>` at all. What
111+
// admitting it grants is core's: the identity and the effect its paired
112+
// spelling runs under are on the declaration that owns the component, so
113+
// there is nothing here for a host to restate or to move.
114114
const entry = gitDirectoryEntry();
115115
expect(entry.name).toBe("Dir");
116-
expect(entry.identity).toEqual({
117-
origin: "@executablemd/workflow/composition",
118-
key: "Dir",
119-
revision: "3",
120-
});
121-
expect(Reflect.get(entry, "legacy")).toEqual(["@executablemd/workflow/composition/dir-v2#Dir"]);
116+
expect(Object.keys(entry)).not.toContain("identity");
117+
expect(Object.keys(entry)).not.toContain("legacy");
122118
// It is the directory capability, not some other entry wearing the name.
123119
expect(Reflect.get(entry, "capability")).toBe("directory:ensure");
124120
});

‎packages/cli/tests/plan-command-document.test.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,12 +1184,7 @@ describe("Tier PI — read-only information requests", () => {
11841184

11851185
/** The admitted entry the barrier is written as. */
11861186
function heldEntry(): FragmentEntry {
1187-
return {
1188-
kind: "component-answer",
1189-
name: "Held",
1190-
identity: { origin: HELD_ORIGIN, key: "Held", revision: "1" },
1191-
forms: ["self-closing"],
1192-
};
1187+
return { kind: "component-answer", name: "Held", forms: ["self-closing"] };
11931188
}
11941189

11951190
/** One barrier: a body whose cleanup blocks until the case releases it. */
@@ -1209,6 +1204,13 @@ describe("Tier PI — read-only information requests", () => {
12091204
kind: "function",
12101205
name: "Held",
12111206
props: { type: "object", properties: {}, additionalProperties: false },
1207+
forms: ["self-closing"],
1208+
generated: {
1209+
"self-closing": {
1210+
identity: "https://executable.md/components/held/read",
1211+
effect: "read",
1212+
},
1213+
},
12121214
*fn(): Operation<Json> {
12131215
// Registered before anything can fail, so the barrier is reached
12141216
// whichever way this projection ends.
@@ -1219,7 +1221,7 @@ describe("Tier PI — read-only information requests", () => {
12191221
return "held";
12201222
},
12211223
},
1222-
{ origin: HELD_ORIGIN, key: "Held", revision: "1" },
1224+
{ origin: HELD_ORIGIN },
12231225
),
12241226
};
12251227
}

‎packages/cli/tests/plan-component.test.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,17 +2027,16 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => {
20272027
* The profile these rows evaluate under: the read entry, and never the wide
20282028
* one.
20292029
*
2030-
* The entry's identity is the fixture's own, so the catalog an agent is shown
2031-
* inside an evaluation names `test://fe713/read` and the admitted capability
2032-
* is core's file read behind it.
2030+
* The entry's name is the fixture's own, so the catalog an agent is shown
2031+
* inside an evaluation names `<FE713.Read>` and the admitted capability is
2032+
* core's file read behind it.
20332033
*/
20342034
function fixtureProfile(): FragmentEvaluationInput {
20352035
return {
20362036
read: [
20372037
{
20382038
...fileReadEntry(),
20392039
name: READ_COMPONENT.name,
2040-
identity: { origin: READ_COMPONENT.origin, key: "FE713.Read", revision: "1" },
20412040
description: READ_COMPONENT.description,
20422041
},
20432042
],
@@ -2163,7 +2162,10 @@ describe("Tier FE — Plan produces text, Evaluate runs it", () => {
21632162
expect(seen).toHaveLength(1);
21642163
const catalog = seen[0] ?? "";
21652164
expect(catalog).toContain(READ_MARKER);
2166-
expect(catalog).toContain(READ_COMPONENT.origin);
2165+
// Reported as the implementation it runs rather than as the name it was
2166+
// admitted under: the entry is core's own file read, which is what the
2167+
// host chose to admit under a name of its own.
2168+
expect(catalog).toContain("@executablemd/core");
21672169
// The wide-only component exists in this scope and is not in the profile,
21682170
// so its absence here is the narrowing rather than the fixture.
21692171
expect(catalog).not.toContain(WIDE_MARKER);

‎packages/cli/tests/workflow-agent.test.ts‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -722,16 +722,21 @@ describe("Tier WAL — the workflow Agent observation loop", () => {
722722
});
723723

724724
expect(attempt.failure).toBe(undefined);
725-
// The standard profile admitted it: one record, naming both write
726-
// identities and the classes the document selected.
725+
// The standard profile admitted it: one record, naming both admitted
726+
// spellings and the classes the document selected.
727727
const recorded = admitted(attempt.events);
728728
expect(recorded).toHaveLength(1);
729729
const policy = JSON.stringify(recorded[0]);
730-
expect(policy).toContain("File:write");
731-
// The identity is a structural record, so the origin, the key and the
732-
// revision are read as themselves rather than as one assembled spelling.
733-
expect(policy).toContain('"origin":"@executablemd/workflow/composition"');
734-
expect(policy).toContain('"key":"Dir","revision":"3"');
730+
// What each spelling runs as comes from the declaration that owns the
731+
// implementation, so the row holds that URL and the table that admitted
732+
// it rather than a spelling a host assembled.
733+
expect(policy).toContain(
734+
'"name":"File","form":"paired","identity":"https://executable.md/components/file/write"',
735+
);
736+
expect(policy).toContain(
737+
'"name":"Dir","form":"paired","identity":"https://executable.md/components/dir/ensure"',
738+
);
739+
expect(policy).toContain('"effect":"write"');
735740
expect(policy).toContain('"allow":["write"]');
736741
// And the change is in the run's own Workspace, where an ordinary read
737742
// beneath the fragment's own directory finds it. Anchored on the

‎packages/cli/tests/workflow-suspension.test.ts‎

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,15 @@ const PROMPT_EFFECT = "agent_prompt"; // packages/core/src/agent/journal.ts
821821
const EVALUATION_EFFECT = "generated_xmd"; // packages/core/src/generated-xmd.ts
822822
const FILE_EFFECT = WORKSPACE_FILE;
823823

824+
/**
825+
* What core's own declaration offers the paired `<Dir>` as.
826+
*
827+
* Spelled here rather than imported, because a case about what a retained
828+
* admission holds has to name the string the journal holds: reading it from the
829+
* component the resume also reads would agree with itself after a rename.
830+
*/
831+
const DIR_ENSURE = "https://executable.md/components/dir/ensure";
832+
824833
/** Every prompt this run's agent was actually asked. */
825834
interface AgentCalls {
826835
readonly prompts: string[];
@@ -1508,10 +1517,10 @@ the retained note
15081517
});
15091518
}
15101519

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

15421551
const suspended = retained(path);
15431552
expect(suspended.status).toBe("suspended");
1544-
// The admission carries the versioned identity. It is a structural record
1545-
// rather than a spelling, and the version is the `revision` member — so
1546-
// that is what this reads, and what the refusal below moves.
1553+
// The admission carries the identity core's own declaration offers the
1554+
// paired spelling as, in the write table that admitted it — so that is what
1555+
// this reads, and what the refusal below moves.
15471556
expect(
15481557
readRecords(path).some(
1549-
(record) => record.includes(`"key":"Dir"`) && record.includes(`"revision":"3"`),
1558+
(record) =>
1559+
record.includes(`"name":"Dir","form":"paired","identity":"${DIR_ENSURE}"`) &&
1560+
record.includes(`"effect":"write"`),
15501561
),
15511562
).toBe(true);
15521563

@@ -1616,19 +1627,18 @@ the retained note
16161627
expect(counts(path)).toEqual(atSuspension);
16171628
});
16181629

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

1623-
// The run becomes one admitted under the former identity. Nothing else
1624-
// changes — same source, roots and selection — so the identity is the only
1625-
// thing the resume can disagree about.
1626-
//
1627-
// The version is the `revision` member, and core's own entries state `2`,
1628-
// so `"revision":"3"` names the Dir entry and nothing else. Moving it back
1629-
// is exactly "admitted under the earlier revision", which is the grant the
1630-
// former placement-only Dir had.
1631-
rewriteRecords(path, (record) => record.replaceAll(`"revision":"3"`, `"revision":"2"`));
1634+
// The run becomes one admitted under a different identity for the same
1635+
// spelling of the same name. Nothing else changes — same source, roots and
1636+
// selection — so the identity is the only thing the resume can disagree
1637+
// about, and it is still a URL this engine can read: the refusal is the
1638+
// grant comparison rather than an unreadable record.
1639+
rewriteRecords(path, (record) =>
1640+
record.replaceAll(DIR_ENSURE, "https://executable.md/components/dir/placement"),
1641+
);
16321642
const beforeResume = counts(path);
16331643
const rootsBeforeResume = workspaceRootState(path);
16341644

@@ -1646,11 +1656,10 @@ the retained note
16461656
expect(workspaceRootState(path)).toEqual(rootsBeforeResume);
16471657
// Nothing after the wait reached the document either.
16481658
expect(rendered).toEqual([]);
1649-
// And the refusal does not publish which identity moved. The identity is
1650-
// four members now, so this reads the two a refusal could carry: the origin
1651-
// that owns the implementation, and the member the rewrite actually moved.
1659+
// And the refusal does not publish which identity moved: neither the one
1660+
// the admission holds nor the one the rewrite put there.
16521661
const reported = resumed.written.err.join("\n");
1653-
expect(reported).not.toContain("@executablemd/workflow/composition");
1654-
expect(reported).not.toContain("revision");
1662+
expect(reported).not.toContain(DIR_ENSURE);
1663+
expect(reported).not.toContain("/components/dir/placement");
16551664
});
16561665
});

‎packages/core/host.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ export type {
9797
FragmentEntry,
9898
FragmentEvaluationInput,
9999
FragmentForm,
100-
FragmentIdentity,
101100
FragmentWorkspaceAccess,
102101
} from "./src/evaluation-profile.ts";
103102
/**
@@ -329,11 +328,11 @@ export {
329328
export type {
330329
GeneratedComponentForm,
331330
GeneratedEffectClass,
331+
GeneratedImplementation,
332332
GeneratedMutation,
333333
GeneratedObservation,
334334
GeneratedRequest,
335335
GeneratedXmdRequest,
336-
RetainedFragmentIdentity,
337336
} from "./src/generated-xmd.ts";
338337

339338
/**

‎packages/core/mod.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,13 @@ export {
286286
registerComponents,
287287
} from "./src/components/registration.ts";
288288
export type { ComponentRegistration } from "./src/components/registration.ts";
289+
// What a registration says about the generated use of its own forms. The two
290+
// members a declaration states; nothing here reads or publishes a catalog of them.
291+
export type {
292+
GeneratedEffect,
293+
GeneratedForm,
294+
GeneratedForms,
295+
} from "./src/components/generated-metadata.ts";
289296
export { DEFAULT_INCLUDES, selectComponent } from "./src/components/select.ts";
290297
export type { SelectOptions } from "./src/components/select.ts";
291298
export { CORE_COMPONENT_NAMES } from "./src/components/registry.ts";

0 commit comments

Comments
 (0)