@agent-teams/docs-protocol 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +68 -10
- package/dist/adapters/adoption-input.d.ts +0 -3
- package/dist/adapters/adoption-input.d.ts.map +1 -1
- package/dist/adapters/adoption-input.js +12 -5
- package/dist/adapters/adoption-input.js.map +1 -1
- package/dist/adapters/adoption-package-identity.d.ts.map +1 -1
- package/dist/adapters/adoption-package-identity.js +4 -3
- package/dist/adapters/adoption-package-identity.js.map +1 -1
- package/dist/adapters/docs-command-envelope-schema-validator.js +3 -3
- package/dist/adapters/docs-command-envelope-schema-validator.js.map +1 -1
- package/dist/adapters/docs-profile-schema-validator.d.ts.map +1 -1
- package/dist/adapters/docs-profile-schema-validator.js +4 -1
- package/dist/adapters/docs-profile-schema-validator.js.map +1 -1
- package/dist/adapters/foundation-docs-port.d.ts +8 -1
- package/dist/adapters/foundation-docs-port.d.ts.map +1 -1
- package/dist/adapters/foundation-docs-port.js +9 -1
- package/dist/adapters/foundation-docs-port.js.map +1 -1
- package/dist/adapters/node-adoption-inspector.d.ts +1 -1
- package/dist/adapters/node-adoption-inspector.d.ts.map +1 -1
- package/dist/adapters/node-adoption-inspector.js +63 -25
- package/dist/adapters/node-adoption-inspector.js.map +1 -1
- package/dist/adapters/node-profile-discovery.d.ts +7 -0
- package/dist/adapters/node-profile-discovery.d.ts.map +1 -0
- package/dist/adapters/node-profile-discovery.js +38 -0
- package/dist/adapters/node-profile-discovery.js.map +1 -0
- package/dist/adapters/node-profile-reader.d.ts +1 -1
- package/dist/adapters/node-profile-reader.d.ts.map +1 -1
- package/dist/adapters/node-profile-reader.js +9 -8
- package/dist/adapters/node-profile-reader.js.map +1 -1
- package/dist/application/docs-execution.d.ts +7 -0
- package/dist/application/docs-execution.d.ts.map +1 -0
- package/dist/application/docs-execution.js +49 -0
- package/dist/application/docs-execution.js.map +1 -0
- package/dist/application/docs-protocol.d.ts +10 -2
- package/dist/application/docs-protocol.d.ts.map +1 -1
- package/dist/application/docs-protocol.js +32 -45
- package/dist/application/docs-protocol.js.map +1 -1
- package/dist/community/bootstrap/index.d.ts +3 -0
- package/dist/community/bootstrap/index.d.ts.map +1 -0
- package/dist/community/bootstrap/index.js +2 -0
- package/dist/community/bootstrap/index.js.map +1 -0
- package/dist/community/bootstrap/portable-bootstrap-assets.d.ts +10 -0
- package/dist/community/bootstrap/portable-bootstrap-assets.d.ts.map +1 -0
- package/dist/community/bootstrap/portable-bootstrap-assets.js +183 -0
- package/dist/community/bootstrap/portable-bootstrap-assets.js.map +1 -0
- package/dist/community/bootstrap/portable-bootstrap.d.ts +48 -0
- package/dist/community/bootstrap/portable-bootstrap.d.ts.map +1 -0
- package/dist/community/bootstrap/portable-bootstrap.js +387 -0
- package/dist/community/bootstrap/portable-bootstrap.js.map +1 -0
- package/dist/community/context/community-query.d.ts +52 -0
- package/dist/community/context/community-query.d.ts.map +1 -0
- package/dist/community/context/community-query.js +306 -0
- package/dist/community/context/community-query.js.map +1 -0
- package/dist/community/context/index.d.ts +7 -0
- package/dist/community/context/index.d.ts.map +1 -0
- package/dist/community/context/index.js +5 -0
- package/dist/community/context/index.js.map +1 -0
- package/dist/community/context/llms-text.d.ts +37 -0
- package/dist/community/context/llms-text.d.ts.map +1 -0
- package/dist/community/context/llms-text.js +130 -0
- package/dist/community/context/llms-text.js.map +1 -0
- package/dist/community/context/minisearch-adapter.d.ts +8 -0
- package/dist/community/context/minisearch-adapter.d.ts.map +1 -0
- package/dist/community/context/minisearch-adapter.js +59 -0
- package/dist/community/context/minisearch-adapter.js.map +1 -0
- package/dist/community/context/ranked-search.d.ts +38 -0
- package/dist/community/context/ranked-search.d.ts.map +1 -0
- package/dist/community/context/ranked-search.js +118 -0
- package/dist/community/context/ranked-search.js.map +1 -0
- package/dist/composition/cli.d.ts +2 -4
- package/dist/composition/cli.d.ts.map +1 -1
- package/dist/composition/cli.js +236 -115
- package/dist/composition/cli.js.map +1 -1
- package/dist/composition/docs-cli-machine.d.ts +9 -0
- package/dist/composition/docs-cli-machine.d.ts.map +1 -0
- package/dist/composition/docs-cli-machine.js +130 -0
- package/dist/composition/docs-cli-machine.js.map +1 -0
- package/dist/composition/docs-human-renderer.d.ts +2 -0
- package/dist/composition/docs-human-renderer.d.ts.map +1 -1
- package/dist/composition/docs-human-renderer.js +18 -1
- package/dist/composition/docs-human-renderer.js.map +1 -1
- package/dist/composition/node-docs-api.d.ts +12 -0
- package/dist/composition/node-docs-api.d.ts.map +1 -1
- package/dist/composition/node-docs-api.js +14 -0
- package/dist/composition/node-docs-api.js.map +1 -1
- package/dist/composition/node-docs-bootstrap-api.d.ts +75 -0
- package/dist/composition/node-docs-bootstrap-api.d.ts.map +1 -0
- package/dist/composition/node-docs-bootstrap-api.js +97 -0
- package/dist/composition/node-docs-bootstrap-api.js.map +1 -0
- package/dist/consumer-integration/adapters/consumer-upgrade-file-projectors.d.ts +11 -0
- package/dist/consumer-integration/adapters/consumer-upgrade-file-projectors.d.ts.map +1 -1
- package/dist/consumer-integration/adapters/consumer-upgrade-file-projectors.js +35 -0
- package/dist/consumer-integration/adapters/consumer-upgrade-file-projectors.js.map +1 -1
- package/dist/consumer-integration/adapters/node-consumer-upgrade-sandbox.d.ts.map +1 -1
- package/dist/consumer-integration/adapters/node-consumer-upgrade-sandbox.js +11 -2
- package/dist/consumer-integration/adapters/node-consumer-upgrade-sandbox.js.map +1 -1
- package/dist/domain/model-v2.d.ts +25 -1
- package/dist/domain/model-v2.d.ts.map +1 -1
- package/dist/domain/model-v3.d.ts +72 -0
- package/dist/domain/model-v3.d.ts.map +1 -0
- package/dist/domain/model-v3.js +2 -0
- package/dist/domain/model-v3.js.map +1 -0
- package/dist/domain/model.d.ts +5 -0
- package/dist/domain/model.d.ts.map +1 -1
- package/dist/domain/model.js +3 -0
- package/dist/domain/model.js.map +1 -1
- package/dist/domain/profile-policy.d.ts +4 -3
- package/dist/domain/profile-policy.d.ts.map +1 -1
- package/dist/domain/profile-policy.js +43 -13
- package/dist/domain/profile-policy.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
- package/schemas/docs-protocol-command-envelope/v3.schema.json +529 -0
- package/schemas/docs-protocol-profile/v3.schema.json +49 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const MAXIMUM_BOOTSTRAP_FILES = 32;
|
|
2
|
+
const SKILL_PATH = ".agents/skills/docs-authoring/SKILL.md";
|
|
3
|
+
export const PORTABLE_BOOTSTRAP_BEGIN_MARKER = "<!-- agent-teams:portable-docs:start -->";
|
|
4
|
+
export const PORTABLE_BOOTSTRAP_END_MARKER = "<!-- agent-teams:portable-docs:end -->";
|
|
5
|
+
function yamlScalar(value) {
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
function docsConfig() {
|
|
9
|
+
return `schemaVersion: 3
|
|
10
|
+
protocol: {id: agent-teams.docs-protocol, version: 1}
|
|
11
|
+
foundationProfile:
|
|
12
|
+
path: .docs-protocol/document-authoring.yaml
|
|
13
|
+
schemaVersion: 3
|
|
14
|
+
metadataSidecarPolicy: foundation-profile-v3-strict-merge
|
|
15
|
+
agentWorkflow:
|
|
16
|
+
skillPath: ${SKILL_PATH}
|
|
17
|
+
adoption: portable-v1
|
|
18
|
+
semanticValidatorIds: []
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
function artifactType(input) {
|
|
22
|
+
const reachability = input.type === "adr"
|
|
23
|
+
? "{kind: manual-fixed-index, indexPath: docs/decisions/README.md}"
|
|
24
|
+
: "{kind: not-required, reason: Deterministic catalog and context projection provide reachability without duplicating manual indexes.}";
|
|
25
|
+
const heading = input.type === "adr" ? "id-colon-title" : "title";
|
|
26
|
+
return ` - type: ${input.type}
|
|
27
|
+
initialStatus: proposed
|
|
28
|
+
identity: ${input.identity}
|
|
29
|
+
placement: {kind: collection, directory: ${input.directory}, filename: ${input.type === "adr" ? "numeric-id-slug" : "slug"}}
|
|
30
|
+
template: {kind: fenced-markdown-body, path: .docs-protocol/templates/${input.template}.md}
|
|
31
|
+
heading: {kind: ${heading}}
|
|
32
|
+
reachability: ${reachability}
|
|
33
|
+
ownerSetId: documentation
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
function documentAuthoringProfile(projectId, ownerId) {
|
|
37
|
+
return `schemaVersion: 3
|
|
38
|
+
projectId: ${yamlScalar(projectId)}
|
|
39
|
+
catalog:
|
|
40
|
+
metadataSchemaPath: .docs-protocol/metadata.schema.json
|
|
41
|
+
ownerCatalog: {path: .docs-protocol/owners.yaml, contract: foundation.owner-map/v1}
|
|
42
|
+
collections:
|
|
43
|
+
- {kind: markdown-tree, root: docs}
|
|
44
|
+
excludedPrefixes: [.docs-protocol]
|
|
45
|
+
authoring:
|
|
46
|
+
mode: create-only
|
|
47
|
+
ownerSets:
|
|
48
|
+
schemaVersion: 1
|
|
49
|
+
sets:
|
|
50
|
+
documentation: [${yamlScalar(ownerId)}]
|
|
51
|
+
artifactTypes:
|
|
52
|
+
${artifactType({ type: "adr", directory: "docs/decisions", template: "adr", identity: "{kind: explicit, format: adr-four-digits}" })}${artifactType({ type: "tutorial", directory: "docs/tutorials", template: "tutorial", identity: "{kind: explicit, format: qualified, grammar: {prefixSegments: [docs, tutorial], minSuffixSegments: 1, maxSuffixSegments: 16}}" })}${artifactType({ type: "how-to", directory: "docs/how-to", template: "how-to", identity: "{kind: explicit, format: qualified, grammar: {prefixSegments: [docs, how-to], minSuffixSegments: 1, maxSuffixSegments: 16}}" })}${artifactType({ type: "reference", directory: "docs/reference", template: "reference", identity: "{kind: explicit, format: qualified, grammar: {prefixSegments: [docs, reference], minSuffixSegments: 1, maxSuffixSegments: 16}}" })}${artifactType({ type: "explanation", directory: "docs/explanation", template: "explanation", identity: "{kind: explicit, format: qualified, grammar: {prefixSegments: [docs, explanation], minSuffixSegments: 1, maxSuffixSegments: 16}}" })}`;
|
|
53
|
+
}
|
|
54
|
+
function metadataSchema(ownerId) {
|
|
55
|
+
const commonStatuses = ["proposed", "active", "deprecated", "superseded"];
|
|
56
|
+
const nonAdrTypes = ["tutorial", "how-to", "reference", "explanation", "index"];
|
|
57
|
+
return `${JSON.stringify({
|
|
58
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
59
|
+
type: "object",
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
required: ["id", "type", "status", "owner", "summary"],
|
|
62
|
+
properties: {
|
|
63
|
+
id: { type: "string", minLength: 1, maxLength: 214 },
|
|
64
|
+
type: { enum: ["adr", "tutorial", "how-to", "reference", "explanation", "index"] },
|
|
65
|
+
status: { type: "string" },
|
|
66
|
+
owner: { const: ownerId },
|
|
67
|
+
summary: { type: "string", minLength: 1, maxLength: 500 },
|
|
68
|
+
related: { type: "array", uniqueItems: true, items: { type: "string" } },
|
|
69
|
+
blocked_by: { type: "array", uniqueItems: true, items: { type: "string" } },
|
|
70
|
+
code_anchors: { type: "array", uniqueItems: true, items: { type: "object" } }
|
|
71
|
+
},
|
|
72
|
+
oneOf: [{
|
|
73
|
+
type: "object",
|
|
74
|
+
required: ["type", "status"],
|
|
75
|
+
properties: {
|
|
76
|
+
type: { const: "adr" },
|
|
77
|
+
status: { enum: [...commonStatuses, "accepted"] }
|
|
78
|
+
}
|
|
79
|
+
}, {
|
|
80
|
+
type: "object",
|
|
81
|
+
required: ["type", "status"],
|
|
82
|
+
properties: {
|
|
83
|
+
type: { enum: nonAdrTypes },
|
|
84
|
+
status: { enum: commonStatuses }
|
|
85
|
+
}
|
|
86
|
+
}]
|
|
87
|
+
}, undefined, 2)}\n`;
|
|
88
|
+
}
|
|
89
|
+
function template(type, title, sections) {
|
|
90
|
+
return `\`\`\`\`markdown
|
|
91
|
+
---
|
|
92
|
+
id: REPLACE_ME
|
|
93
|
+
type: ${type}
|
|
94
|
+
status: proposed
|
|
95
|
+
owner: REPLACE_ME
|
|
96
|
+
summary: REPLACE_ME
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
# ${title}
|
|
100
|
+
|
|
101
|
+
${sections.map((section) => `## ${section}\n\nREPLACE_ME`).join("\n\n")}
|
|
102
|
+
\`\`\`\`
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
function indexReadme(input) {
|
|
106
|
+
return `---
|
|
107
|
+
id: ${input.id}
|
|
108
|
+
type: index
|
|
109
|
+
status: active
|
|
110
|
+
owner: ${yamlScalar(input.ownerId)}
|
|
111
|
+
summary: ${input.description}
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# ${input.title}
|
|
115
|
+
|
|
116
|
+
${input.description}
|
|
117
|
+
|
|
118
|
+
This index describes the category. The deterministic documentation catalog is the source for document discovery, so this file does not duplicate a hand-maintained document list.
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
function skill() {
|
|
122
|
+
return `---
|
|
123
|
+
name: docs-authoring
|
|
124
|
+
description: Create and maintain repository documentation using the portable Docs Protocol layout.
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
# Documentation authoring
|
|
128
|
+
|
|
129
|
+
Protocol declaration: agent-teams.docs-protocol/v1.
|
|
130
|
+
|
|
131
|
+
Read \`docs.config.yaml\` and the local authority files before changing documentation.
|
|
132
|
+
Choose the Diataxis category matching the reader's need, or use an ADR for a durable decision.
|
|
133
|
+
|
|
134
|
+
1. Discover existing material with \`docs-protocol find --consumer . --profile docs.config.yaml --text QUERY\`.
|
|
135
|
+
2. Preview creation with \`docs-protocol new --consumer . --profile docs.config.yaml --type TYPE --id ID --title "TITLE" --owner OWNER_ID --summary "SUMMARY" --dry-run\`.
|
|
136
|
+
3. Apply the reviewed intent with \`docs-protocol new --consumer . --profile docs.config.yaml --type TYPE --id ID --title "TITLE" --owner OWNER_ID --summary "SUMMARY" --apply\`.
|
|
137
|
+
4. If the result is \`manual-required\`, add its exact \`markdownLink\` to its exact \`indexPath\` before verification.
|
|
138
|
+
5. Refresh bounded agent context with \`docs-protocol context --consumer . --profile docs.config.yaml\`.
|
|
139
|
+
6. Verify authorities and documents with \`docs-protocol check --consumer . --profile docs.config.yaml\`.
|
|
140
|
+
|
|
141
|
+
Start from the matching template in \`.docs-protocol/templates\` and replace every placeholder.
|
|
142
|
+
Keep metadata valid against \`.docs-protocol/metadata.schema.json\` and select a declared owner.
|
|
143
|
+
Create files only and never overwrite an existing document for a new-document request.
|
|
144
|
+
Do not assume a package manager, hosting provider, or repository forge.
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
export function portableBootstrapManagedBlock(eol) {
|
|
148
|
+
return [
|
|
149
|
+
PORTABLE_BOOTSTRAP_BEGIN_MARKER,
|
|
150
|
+
`Use [${SKILL_PATH}](${SKILL_PATH}) for documentation.`,
|
|
151
|
+
PORTABLE_BOOTSTRAP_END_MARKER
|
|
152
|
+
].join(eol);
|
|
153
|
+
}
|
|
154
|
+
export function portableBootstrapDesiredFiles(projectId, ownerId) {
|
|
155
|
+
const textFiles = [
|
|
156
|
+
["docs.config.yaml", docsConfig()],
|
|
157
|
+
[".docs-protocol/document-authoring.yaml", documentAuthoringProfile(projectId, ownerId)],
|
|
158
|
+
[".docs-protocol/metadata.schema.json", metadataSchema(ownerId)],
|
|
159
|
+
[".docs-protocol/owners.yaml", `version: 1\nowners:\n ${yamlScalar(ownerId)}:\n kind: documentation\n`],
|
|
160
|
+
[".docs-protocol/templates/adr.md", template("adr", "ADR-NNNN: Decision title", ["Context", "Decision", "Consequences"])],
|
|
161
|
+
[".docs-protocol/templates/tutorial.md", template("tutorial", "Tutorial title", ["Goal", "Prerequisites", "Steps", "Next steps"])],
|
|
162
|
+
[".docs-protocol/templates/how-to.md", template("how-to", "How to achieve a goal", ["Before you begin", "Procedure", "Verification"])],
|
|
163
|
+
[".docs-protocol/templates/reference.md", template("reference", "Reference title", ["Overview", "Contract", "Examples"])],
|
|
164
|
+
[".docs-protocol/templates/explanation.md", template("explanation", "Explanation title", ["Context", "Concept", "Trade-offs"])],
|
|
165
|
+
["docs/README.md", indexReadme({ id: "docs.index", ownerId, title: "Documentation", description: "Use tutorials to learn, how-to guides to complete tasks, reference for exact facts, explanations for understanding, and decision records for architectural choices." })],
|
|
166
|
+
["docs/decisions/README.md", indexReadme({ id: "docs.decisions.index", ownerId, title: "Architecture decisions", description: "MADR-compatible decision records capture durable architectural choices, their context, and their consequences." })],
|
|
167
|
+
["docs/tutorials/README.md", indexReadme({ id: "docs.tutorials.index", ownerId, title: "Tutorials", description: "Tutorials are learning-oriented lessons that guide a reader to a successful first result." })],
|
|
168
|
+
["docs/how-to/README.md", indexReadme({ id: "docs.how-to.index", ownerId, title: "How-to guides", description: "How-to guides are goal-oriented procedures for readers who already understand the basics." })],
|
|
169
|
+
["docs/reference/README.md", indexReadme({ id: "docs.reference.index", ownerId, title: "Reference", description: "Reference documentation is precise, complete, and organized for lookup." })],
|
|
170
|
+
["docs/explanation/README.md", indexReadme({ id: "docs.explanation.index", ownerId, title: "Explanation", description: "Explanations build understanding by describing concepts, context, and trade-offs." })],
|
|
171
|
+
[SKILL_PATH, skill()]
|
|
172
|
+
];
|
|
173
|
+
const files = textFiles.map(([path, source]) => Object.freeze({
|
|
174
|
+
path,
|
|
175
|
+
ownership: "create-only",
|
|
176
|
+
bytes: Buffer.from(source, "utf8")
|
|
177
|
+
}));
|
|
178
|
+
if (files.length + 1 > MAXIMUM_BOOTSTRAP_FILES) {
|
|
179
|
+
throw new TypeError(`Portable bootstrap exceeds ${MAXIMUM_BOOTSTRAP_FILES} files.`);
|
|
180
|
+
}
|
|
181
|
+
return Object.freeze(files);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=portable-bootstrap-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-bootstrap-assets.js","sourceRoot":"","sources":["../../../src/community/bootstrap/portable-bootstrap-assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type KnownFileTransactionPlanV1, type KnownFileTransactionReceiptV1 } from "@agent-teams/engineering-foundation/mutation";
|
|
2
|
+
type PortableBootstrapMode = "dry-run" | "apply";
|
|
3
|
+
type PortableBootstrapWriteState = "blocked" | "create" | "current" | "replace";
|
|
4
|
+
interface PortableBootstrapInput {
|
|
5
|
+
readonly consumerRoot: string;
|
|
6
|
+
readonly mode: PortableBootstrapMode;
|
|
7
|
+
readonly ownerId: string;
|
|
8
|
+
readonly projectId: string;
|
|
9
|
+
}
|
|
10
|
+
interface ApplyPortableBootstrapInput extends PortableBootstrapInput {
|
|
11
|
+
readonly expectedPlanDigest: `sha256:${string}`;
|
|
12
|
+
readonly mode: "apply";
|
|
13
|
+
}
|
|
14
|
+
interface PortableBootstrapFilePlan {
|
|
15
|
+
readonly ownership: "create-only" | "managed-block";
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly writeState: PortableBootstrapWriteState;
|
|
18
|
+
}
|
|
19
|
+
interface PortableBootstrapIssue {
|
|
20
|
+
readonly code: "PORTABLE_BOOTSTRAP_AGENTS_TOO_LARGE" | "PORTABLE_BOOTSTRAP_CONFLICT" | "PORTABLE_BOOTSTRAP_INVALID_AGENTS_MARKERS";
|
|
21
|
+
readonly message: string;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
}
|
|
24
|
+
export interface PortableBootstrapPlan {
|
|
25
|
+
readonly schemaVersion: 1;
|
|
26
|
+
readonly protocol: "agent-teams.docs-protocol.portable-bootstrap/v1";
|
|
27
|
+
readonly mode: PortableBootstrapMode;
|
|
28
|
+
readonly outcome: "blocked" | "change-required" | "current";
|
|
29
|
+
readonly planDigest: `sha256:${string}`;
|
|
30
|
+
readonly files: readonly PortableBootstrapFilePlan[];
|
|
31
|
+
readonly issues: readonly PortableBootstrapIssue[];
|
|
32
|
+
readonly transactionPlan?: KnownFileTransactionPlanV1;
|
|
33
|
+
}
|
|
34
|
+
interface PortableBootstrapExecution {
|
|
35
|
+
readonly outcome: "applied" | "current";
|
|
36
|
+
readonly plan: PortableBootstrapPlan;
|
|
37
|
+
readonly receipt: KnownFileTransactionReceiptV1;
|
|
38
|
+
}
|
|
39
|
+
export declare function compilePortableBootstrap(input: PortableBootstrapInput): Promise<PortableBootstrapPlan>;
|
|
40
|
+
export declare function applyPortableBootstrap(input: ApplyPortableBootstrapInput): Promise<PortableBootstrapExecution>;
|
|
41
|
+
export declare function inspectPortableBootstrap(input: {
|
|
42
|
+
readonly consumerRoot: string;
|
|
43
|
+
}): Promise<import("@agent-teams/engineering-foundation/mutation").KnownFileTransactionBarrierInspection>;
|
|
44
|
+
export declare function recoverPortableBootstrap(input: {
|
|
45
|
+
readonly consumerRoot: string;
|
|
46
|
+
}): Promise<KnownFileTransactionReceiptV1>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=portable-bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/community/bootstrap/portable-bootstrap.ts"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EACnC,MAAM,8CAA8C,CAAC;AAetD,KAAK,qBAAqB,GAAG,SAAS,GAAG,OAAO,CAAC;AACjD,KAAK,2BAA2B,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhF,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,UAAU,2BAA4B,SAAQ,sBAAsB;IAClE,QAAQ,CAAC,kBAAkB,EAAE,UAAU,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,yBAAyB;IACjC,QAAQ,CAAC,SAAS,EAAE,aAAa,GAAG,eAAe,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;CAClD;AAED,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,IAAI,EACT,qCAAqC,GACrC,6BAA6B,GAC7B,2CAA2C,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,iDAAiD,CAAC;IACrE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAAC;IAC5D,QAAQ,CAAC,UAAU,EAAE,UAAU,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,yBAAyB,EAAE,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACnD,QAAQ,CAAC,eAAe,CAAC,EAAE,0BAA0B,CAAC;CACvD;AAED,UAAU,0BAA0B;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,6BAA6B,CAAC;CACjD;AAwVD,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA8C5G;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAqBpH;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,yGAEtF;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,0CAEtF"}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, open, readdir, realpath } from "node:fs/promises";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { applyKnownFileTransaction, compileKnownFileTransactionPlan, inspectKnownFileTransactionBarrier, recoverKnownFileTransaction } from "@agent-teams/engineering-foundation/mutation";
|
|
6
|
+
import { DOCS_ADOPTION_MAX_ROUTING_BYTES } from "../../domain/model.js";
|
|
7
|
+
import { PORTABLE_BOOTSTRAP_BEGIN_MARKER, PORTABLE_BOOTSTRAP_END_MARKER, portableBootstrapDesiredFiles, portableBootstrapManagedBlock } from "./portable-bootstrap-assets.js";
|
|
8
|
+
const MAXIMUM_OBSERVED_FILE_BYTES = 8 * 1024 * 1024;
|
|
9
|
+
const MAXIMUM_ROOT_LENGTH = 4096, MAXIMUM_PROJECT_ID_LENGTH = 160, MAXIMUM_OWNER_ID_LENGTH = 214;
|
|
10
|
+
const PROJECT_ID = /^[a-z0-9][a-z0-9._/-]*$/u;
|
|
11
|
+
const OWNER_ID = /^[A-Za-z0-9@][A-Za-z0-9@._/-]*$/u;
|
|
12
|
+
function assertCanonicalId(value, name, pattern, maximumLength) {
|
|
13
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maximumLength ||
|
|
14
|
+
value.normalize("NFC") !== value || !pattern.test(value) || value.startsWith("/") ||
|
|
15
|
+
value.endsWith("/") || value.includes("//") || value.split("/").some((part) => part === "." || part === "..")) {
|
|
16
|
+
throw new TypeError(`${name} must be one bounded canonical identifier.`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function assertInput(input) {
|
|
20
|
+
if (Object.getPrototypeOf(input) !== Object.prototype) {
|
|
21
|
+
throw new TypeError("Portable bootstrap input must be a plain object.");
|
|
22
|
+
}
|
|
23
|
+
if (typeof input.consumerRoot !== "string" || input.consumerRoot.length === 0 ||
|
|
24
|
+
input.consumerRoot.length > MAXIMUM_ROOT_LENGTH || input.consumerRoot.includes("\u0000")) {
|
|
25
|
+
throw new TypeError("consumerRoot must be one bounded filesystem path.");
|
|
26
|
+
}
|
|
27
|
+
if (input.mode !== "dry-run" && input.mode !== "apply") {
|
|
28
|
+
throw new TypeError("mode must be dry-run or apply.");
|
|
29
|
+
}
|
|
30
|
+
assertCanonicalId(input.projectId, "projectId", PROJECT_ID, MAXIMUM_PROJECT_ID_LENGTH);
|
|
31
|
+
assertCanonicalId(input.ownerId, "ownerId", OWNER_ID, MAXIMUM_OWNER_ID_LENGTH);
|
|
32
|
+
}
|
|
33
|
+
function sameIdentity(left, right) {
|
|
34
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
35
|
+
}
|
|
36
|
+
function sameObservation(left, right) {
|
|
37
|
+
return sameIdentity(left, right) &&
|
|
38
|
+
left.birthtimeNs === right.birthtimeNs &&
|
|
39
|
+
left.ctimeNs === right.ctimeNs &&
|
|
40
|
+
left.mtimeNs === right.mtimeNs &&
|
|
41
|
+
left.nlink === right.nlink &&
|
|
42
|
+
left.size === right.size;
|
|
43
|
+
}
|
|
44
|
+
async function readAtMost(handle) {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
let total = 0;
|
|
47
|
+
while (total <= MAXIMUM_OBSERVED_FILE_BYTES) {
|
|
48
|
+
const remaining = MAXIMUM_OBSERVED_FILE_BYTES + 1 - total;
|
|
49
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
|
|
50
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);
|
|
51
|
+
if (bytesRead === 0) {
|
|
52
|
+
return Buffer.concat(chunks, total);
|
|
53
|
+
}
|
|
54
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
55
|
+
total += bytesRead;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
async function readObservedHandle(handle, path) {
|
|
60
|
+
const opened = await handle.stat({ bigint: true });
|
|
61
|
+
if (!opened.isFile()) {
|
|
62
|
+
throw new TypeError(`Portable bootstrap target must be a real regular file: ${path}.`);
|
|
63
|
+
}
|
|
64
|
+
if (opened.nlink !== 1n) {
|
|
65
|
+
throw new TypeError(`Portable bootstrap target must not have multiple hard links: ${path}.`);
|
|
66
|
+
}
|
|
67
|
+
if (opened.size > BigInt(MAXIMUM_OBSERVED_FILE_BYTES)) {
|
|
68
|
+
throw new TypeError(`Portable bootstrap target exceeds the bounded read limit: ${path}.`);
|
|
69
|
+
}
|
|
70
|
+
const bytes = await readAtMost(handle);
|
|
71
|
+
if (bytes === undefined) {
|
|
72
|
+
throw new TypeError(`Portable bootstrap target exceeds the bounded read limit: ${path}.`);
|
|
73
|
+
}
|
|
74
|
+
const after = await handle.stat({ bigint: true });
|
|
75
|
+
const physical = await realpath(path).catch(() => null);
|
|
76
|
+
const pathState = physical === path
|
|
77
|
+
? await lstat(path, { bigint: true }).catch(() => null)
|
|
78
|
+
: null;
|
|
79
|
+
if (!after.isFile() || after.nlink !== 1n || !sameObservation(opened, after) ||
|
|
80
|
+
after.size !== BigInt(bytes.byteLength) || pathState === null ||
|
|
81
|
+
pathState.isSymbolicLink() || !pathState.isFile() || pathState.nlink !== 1n ||
|
|
82
|
+
!sameObservation(after, pathState)) {
|
|
83
|
+
throw new TypeError(`Portable bootstrap target changed during read: ${path}.`);
|
|
84
|
+
}
|
|
85
|
+
return { bytes, mode: Number(after.mode & 511n) };
|
|
86
|
+
}
|
|
87
|
+
async function canonicalConsumerRoot(consumerRoot) {
|
|
88
|
+
const requested = resolve(consumerRoot);
|
|
89
|
+
const physical = await realpath(requested);
|
|
90
|
+
const [requestedMetadata, physicalMetadata] = await Promise.all([
|
|
91
|
+
lstat(requested, { bigint: true }),
|
|
92
|
+
lstat(physical, { bigint: true })
|
|
93
|
+
]);
|
|
94
|
+
if (requestedMetadata.isSymbolicLink() || !requestedMetadata.isDirectory() ||
|
|
95
|
+
physicalMetadata.isSymbolicLink() || !physicalMetadata.isDirectory() ||
|
|
96
|
+
!sameIdentity(requestedMetadata, physicalMetadata)) {
|
|
97
|
+
throw new TypeError("Portable bootstrap consumerRoot must be one real directory, not a symlink.");
|
|
98
|
+
}
|
|
99
|
+
return physical;
|
|
100
|
+
}
|
|
101
|
+
async function containedTarget(root, repositoryPath) {
|
|
102
|
+
const segments = repositoryPath.split("/");
|
|
103
|
+
let current = root;
|
|
104
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
105
|
+
const segment = segments[index];
|
|
106
|
+
const identity = segment.normalize("NFC").toLowerCase();
|
|
107
|
+
const entries = await readdir(current);
|
|
108
|
+
const matches = entries.filter((entry) => entry.normalize("NFC").toLowerCase() === identity);
|
|
109
|
+
if (matches.some((entry) => entry !== segment)) {
|
|
110
|
+
throw new TypeError(`Portable bootstrap target has a case or Unicode path alias: ${repositoryPath}.`);
|
|
111
|
+
}
|
|
112
|
+
if (!matches.includes(segment)) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const next = join(current, segment);
|
|
116
|
+
if (index === segments.length - 1) {
|
|
117
|
+
return next;
|
|
118
|
+
}
|
|
119
|
+
const metadata = await lstat(next);
|
|
120
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory() || await realpath(next) !== next) {
|
|
121
|
+
throw new TypeError(`Portable bootstrap target has an unsafe repository ancestor: ${repositoryPath}.`);
|
|
122
|
+
}
|
|
123
|
+
current = next;
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
async function observe(root, repositoryPath) {
|
|
128
|
+
const path = await containedTarget(root, repositoryPath);
|
|
129
|
+
if (path === undefined) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const noFollow = process.platform === "win32" ? 0 : constants.O_NOFOLLOW;
|
|
133
|
+
const nonBlocking = process.platform === "win32" ? 0 : constants.O_NONBLOCK;
|
|
134
|
+
const handle = await open(path, constants.O_RDONLY | noFollow | nonBlocking).catch((error) => {
|
|
135
|
+
if (error.code === "ENOENT") {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (error.code === "ELOOP") {
|
|
139
|
+
throw new TypeError(`Portable bootstrap target must be a real regular file: ${path}.`);
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
});
|
|
143
|
+
if (handle === null) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
return await readObservedHandle(handle, path);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await handle.close();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function count(source, needle) {
|
|
154
|
+
return source.split(needle).length - 1;
|
|
155
|
+
}
|
|
156
|
+
function portablePlanDigest(input) {
|
|
157
|
+
const body = JSON.stringify({
|
|
158
|
+
protocol: "agent-teams.docs-protocol.portable-bootstrap/v1",
|
|
159
|
+
projectId: input.projectId,
|
|
160
|
+
ownerId: input.ownerId,
|
|
161
|
+
files: input.desired.map(({ bytes, ownership, path }) => ({
|
|
162
|
+
path,
|
|
163
|
+
ownership,
|
|
164
|
+
digest: createHash("sha256").update(bytes).digest("hex")
|
|
165
|
+
})),
|
|
166
|
+
agentsManagedBlockDigest: createHash("sha256").update(portableBootstrapManagedBlock("\n")).digest("hex"),
|
|
167
|
+
observedPlanDigest: input.transactionPlan?.planDigest ?? null,
|
|
168
|
+
observations: input.files.map(({ ownership, path, writeState }) => ({ ownership, path, writeState })),
|
|
169
|
+
issues: input.issues
|
|
170
|
+
});
|
|
171
|
+
return `sha256:${createHash("sha256").update(body).digest("hex")}`;
|
|
172
|
+
}
|
|
173
|
+
function planCreateOnlyFile(file, current) {
|
|
174
|
+
if (current === undefined) {
|
|
175
|
+
return {
|
|
176
|
+
file: { path: file.path, ownership: file.ownership, writeState: "create" },
|
|
177
|
+
operation: {
|
|
178
|
+
path: file.path,
|
|
179
|
+
precondition: { state: "absent" },
|
|
180
|
+
postimage: { bytes: file.bytes }
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (current.bytes.equals(file.bytes)) {
|
|
185
|
+
return {
|
|
186
|
+
file: { path: file.path, ownership: file.ownership, writeState: "current" },
|
|
187
|
+
operation: {
|
|
188
|
+
path: file.path,
|
|
189
|
+
precondition: { state: "known-file", acceptedPreimages: [{ bytes: current.bytes, mode: current.mode }] },
|
|
190
|
+
postimage: { bytes: current.bytes, mode: current.mode }
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
file: { path: file.path, ownership: file.ownership, writeState: "blocked" },
|
|
196
|
+
issue: {
|
|
197
|
+
code: "PORTABLE_BOOTSTRAP_CONFLICT",
|
|
198
|
+
path: file.path,
|
|
199
|
+
message: "A create-only bootstrap target already exists with different bytes."
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function agentsSizeIssue(bytes) {
|
|
204
|
+
return bytes.byteLength > DOCS_ADOPTION_MAX_ROUTING_BYTES ? {
|
|
205
|
+
code: "PORTABLE_BOOTSTRAP_AGENTS_TOO_LARGE",
|
|
206
|
+
path: "AGENTS.md",
|
|
207
|
+
message: `Managed AGENTS.md postimage exceeds the ${DOCS_ADOPTION_MAX_ROUTING_BYTES} byte adoption limit.`
|
|
208
|
+
} : undefined;
|
|
209
|
+
}
|
|
210
|
+
function hasExactManagedBlock(input) {
|
|
211
|
+
const begin = input.source.indexOf(PORTABLE_BOOTSTRAP_BEGIN_MARKER);
|
|
212
|
+
const end = input.source.indexOf(PORTABLE_BOOTSTRAP_END_MARKER);
|
|
213
|
+
return input.beginCount === 1 && input.endCount === 1 && input.source.includes(input.block) &&
|
|
214
|
+
begin < end && (begin === 0 || input.source.slice(0, begin).endsWith(input.eol)) &&
|
|
215
|
+
(end + PORTABLE_BOOTSTRAP_END_MARKER.length === input.source.length ||
|
|
216
|
+
input.source.slice(end + PORTABLE_BOOTSTRAP_END_MARKER.length).startsWith(input.eol));
|
|
217
|
+
}
|
|
218
|
+
function planAgentsFile(agents) {
|
|
219
|
+
if (agents === undefined) {
|
|
220
|
+
const bytes = Buffer.from(`${portableBootstrapManagedBlock("\n")}\n`, "utf8");
|
|
221
|
+
return {
|
|
222
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "create" },
|
|
223
|
+
operation: {
|
|
224
|
+
path: "AGENTS.md",
|
|
225
|
+
precondition: { state: "absent" },
|
|
226
|
+
postimage: { bytes }
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const currentSizeIssue = agentsSizeIssue(agents.bytes);
|
|
231
|
+
if (currentSizeIssue !== undefined) {
|
|
232
|
+
return {
|
|
233
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "blocked" },
|
|
234
|
+
issue: currentSizeIssue
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
let source;
|
|
238
|
+
try {
|
|
239
|
+
source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(agents.bytes);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return {
|
|
243
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "blocked" },
|
|
244
|
+
issue: {
|
|
245
|
+
code: "PORTABLE_BOOTSTRAP_INVALID_AGENTS_MARKERS",
|
|
246
|
+
path: "AGENTS.md",
|
|
247
|
+
message: "AGENTS.md must be strict UTF-8 text without BOM or NUL bytes."
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (source.startsWith("\uFEFF") || source.includes("\u0000")) {
|
|
252
|
+
return {
|
|
253
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "blocked" },
|
|
254
|
+
issue: {
|
|
255
|
+
code: "PORTABLE_BOOTSTRAP_INVALID_AGENTS_MARKERS",
|
|
256
|
+
path: "AGENTS.md",
|
|
257
|
+
message: "AGENTS.md must be strict UTF-8 text without BOM or NUL bytes."
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const beginCount = count(source, PORTABLE_BOOTSTRAP_BEGIN_MARKER);
|
|
262
|
+
const endCount = count(source, PORTABLE_BOOTSTRAP_END_MARKER);
|
|
263
|
+
const eol = source.includes("\r\n") && !source.replaceAll("\r\n", "").includes("\n")
|
|
264
|
+
? "\r\n"
|
|
265
|
+
: "\n";
|
|
266
|
+
const block = portableBootstrapManagedBlock(eol);
|
|
267
|
+
if (beginCount === 0 && endCount === 0) {
|
|
268
|
+
const separator = source.length === 0 ? "" : source.endsWith(`${eol}${eol}`) ? "" :
|
|
269
|
+
source.endsWith(eol) ? eol : `${eol}${eol}`;
|
|
270
|
+
const postimage = Buffer.concat([agents.bytes, Buffer.from(`${separator}${block}${eol}`, "utf8")]);
|
|
271
|
+
const postimageSizeIssue = agentsSizeIssue(postimage);
|
|
272
|
+
if (postimageSizeIssue !== undefined) {
|
|
273
|
+
return {
|
|
274
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "blocked" },
|
|
275
|
+
issue: postimageSizeIssue
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "replace" },
|
|
280
|
+
operation: {
|
|
281
|
+
path: "AGENTS.md",
|
|
282
|
+
precondition: { state: "known-file", acceptedPreimages: [{ bytes: agents.bytes, mode: agents.mode }] },
|
|
283
|
+
postimage: { bytes: postimage, mode: agents.mode }
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const exactBlock = hasExactManagedBlock({ beginCount, block, endCount, eol, source });
|
|
288
|
+
if (exactBlock) {
|
|
289
|
+
return {
|
|
290
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "current" },
|
|
291
|
+
operation: {
|
|
292
|
+
path: "AGENTS.md",
|
|
293
|
+
precondition: { state: "known-file", acceptedPreimages: [{ bytes: agents.bytes, mode: agents.mode }] },
|
|
294
|
+
postimage: { bytes: agents.bytes, mode: agents.mode }
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
file: { path: "AGENTS.md", ownership: "managed-block", writeState: "blocked" },
|
|
300
|
+
issue: {
|
|
301
|
+
code: "PORTABLE_BOOTSTRAP_INVALID_AGENTS_MARKERS",
|
|
302
|
+
path: "AGENTS.md",
|
|
303
|
+
message: "AGENTS.md contains duplicate, incomplete, or modified portable documentation markers."
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
export async function compilePortableBootstrap(input) {
|
|
308
|
+
assertInput(input);
|
|
309
|
+
const root = await canonicalConsumerRoot(input.consumerRoot);
|
|
310
|
+
const desired = portableBootstrapDesiredFiles(input.projectId, input.ownerId);
|
|
311
|
+
const operations = [];
|
|
312
|
+
const files = [];
|
|
313
|
+
const issues = [];
|
|
314
|
+
for (const file of desired) {
|
|
315
|
+
const planned = planCreateOnlyFile(file, await observe(root, file.path));
|
|
316
|
+
files.push(planned.file);
|
|
317
|
+
if (planned.operation !== undefined) {
|
|
318
|
+
operations.push(planned.operation);
|
|
319
|
+
}
|
|
320
|
+
if (planned.issue !== undefined) {
|
|
321
|
+
issues.push(planned.issue);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const agents = planAgentsFile(await observe(root, "AGENTS.md"));
|
|
325
|
+
files.push(agents.file);
|
|
326
|
+
if (agents.operation !== undefined) {
|
|
327
|
+
operations.push(agents.operation);
|
|
328
|
+
}
|
|
329
|
+
if (agents.issue !== undefined) {
|
|
330
|
+
issues.push(agents.issue);
|
|
331
|
+
}
|
|
332
|
+
files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
333
|
+
issues.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
334
|
+
const transactionPlan = issues.length === 0
|
|
335
|
+
? compileKnownFileTransactionPlan({ operations })
|
|
336
|
+
: undefined;
|
|
337
|
+
const mutationRequired = files.some(({ writeState }) => writeState === "create" || writeState === "replace");
|
|
338
|
+
const outcome = issues.length > 0 ? "blocked" : mutationRequired ? "change-required" : "current";
|
|
339
|
+
const frozenFiles = Object.freeze(files.map((file) => Object.freeze(file)));
|
|
340
|
+
const frozenIssues = Object.freeze(issues.map((issue) => Object.freeze(issue)));
|
|
341
|
+
return Object.freeze({
|
|
342
|
+
schemaVersion: 1,
|
|
343
|
+
protocol: "agent-teams.docs-protocol.portable-bootstrap/v1",
|
|
344
|
+
mode: input.mode,
|
|
345
|
+
outcome,
|
|
346
|
+
planDigest: portablePlanDigest({
|
|
347
|
+
desired,
|
|
348
|
+
transactionPlan,
|
|
349
|
+
projectId: input.projectId,
|
|
350
|
+
ownerId: input.ownerId,
|
|
351
|
+
files: frozenFiles,
|
|
352
|
+
issues: frozenIssues
|
|
353
|
+
}),
|
|
354
|
+
files: frozenFiles,
|
|
355
|
+
issues: frozenIssues,
|
|
356
|
+
...(transactionPlan === undefined ? {} : { transactionPlan })
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
export async function applyPortableBootstrap(input) {
|
|
360
|
+
if (typeof input.expectedPlanDigest !== "string" ||
|
|
361
|
+
!/^sha256:[0-9a-f]{64}$/u.test(input.expectedPlanDigest)) {
|
|
362
|
+
throw new TypeError("expectedPlanDigest must be the exact digest returned by dry-run.");
|
|
363
|
+
}
|
|
364
|
+
const compiled = await compilePortableBootstrap(input);
|
|
365
|
+
if (compiled.outcome === "blocked" || compiled.transactionPlan === undefined) {
|
|
366
|
+
throw new Error("Portable bootstrap is blocked by conflicting repository files.");
|
|
367
|
+
}
|
|
368
|
+
if (input.expectedPlanDigest !== compiled.planDigest) {
|
|
369
|
+
throw new Error(`Portable bootstrap Plan is stale: expected ${input.expectedPlanDigest}, observed ${compiled.planDigest}.`);
|
|
370
|
+
}
|
|
371
|
+
const receipt = await applyKnownFileTransaction({
|
|
372
|
+
consumerRoot: input.consumerRoot,
|
|
373
|
+
plan: compiled.transactionPlan
|
|
374
|
+
});
|
|
375
|
+
return Object.freeze({
|
|
376
|
+
outcome: receipt.outcome === "applied" ? "applied" : "current",
|
|
377
|
+
plan: compiled,
|
|
378
|
+
receipt
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
export async function inspectPortableBootstrap(input) {
|
|
382
|
+
return inspectKnownFileTransactionBarrier(input);
|
|
383
|
+
}
|
|
384
|
+
export async function recoverPortableBootstrap(input) {
|
|
385
|
+
return recoverKnownFileTransaction(input);
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=portable-bootstrap.js.map
|