@kungfu-tech/buildchain 4.1.3-alpha.0 → 4.1.3-alpha.1
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/architecture/agent-change-map.md +6 -2
- package/architecture/internal-capabilities.json +9 -2
- package/architecture/maintainability-policy.json +3 -3
- package/architecture/release-topology.json +4 -1
- package/architecture/universal-workflow-capability-policy.json +5 -5
- package/contracts/release-discussion-v1.schema.json +3 -0
- package/dist/readers/release-discussion.cjs +4 -0
- package/dist/site/buildchain-contract.json +4 -4
- package/dist/site/buildchain-site.json +14 -9
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +3 -3
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +2 -2
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +4 -4
- package/docs/node-api-reference.md +1 -1
- package/docs/release-discussions.md +44 -5
- package/package.json +1 -1
- package/packages/core/providers/github/discussions/materials.js +16 -5
- package/packages/core/providers/github/discussions/transport.js +16 -5
- package/packages/core/release/discussion/binary.js +11 -0
- package/packages/core/release/discussion/checkpoints.js +45 -4
- package/packages/core/release/discussion/envelope.js +1 -0
- package/packages/core/release/discussion/evidence.js +51 -0
- package/packages/core/release/discussion/presentation.js +92 -0
- package/packages/core/release/discussion/publication.js +3 -1
- package/packages/core/release/discussion/qualification.js +1 -0
- package/packages/core/release/discussion/reader.js +9 -0
- package/packages/core/release/discussion/session.js +2 -0
- package/packages/core/release/discussion/store.js +28 -31
- package/packages/core/release/discussion/threads.js +83 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readDiscussionThreads, eventParent } from "./threads.js";
|
|
2
|
+
import { renderIntent, renderEvent } from "./presentation.js";
|
|
1
3
|
import { collectDiscussionPages } from "../../providers/github/discussions/transport.js";
|
|
2
4
|
import {
|
|
3
5
|
canonicalJson,
|
|
@@ -95,11 +97,10 @@ export function releaseDiscussionStore(
|
|
|
95
97
|
discussion = await transport.create({
|
|
96
98
|
repositoryId: repository.id,
|
|
97
99
|
categoryId: selected[0].id,
|
|
98
|
-
title:
|
|
99
|
-
|
|
100
|
-
intent
|
|
101
|
-
|
|
102
|
-
),
|
|
100
|
+
title: intent.source?.qualification
|
|
101
|
+
? `Buildchain qualification: ${intent.source.qualification}`
|
|
102
|
+
: `Buildchain release: ${intent.key}`,
|
|
103
|
+
body: encodeRecord(intent, renderIntent(intent)),
|
|
103
104
|
});
|
|
104
105
|
} catch (error) {
|
|
105
106
|
discussion = await observeAfterUnknown(find, error);
|
|
@@ -110,39 +111,30 @@ export function releaseDiscussionStore(
|
|
|
110
111
|
}
|
|
111
112
|
async function read(session) {
|
|
112
113
|
const discussion = await transport.get(session.discussion.id);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const trusted = comments.filter(
|
|
118
|
-
(comment) => comment.author?.id === session.writerId,
|
|
114
|
+
const intent = assertDiscussion(
|
|
115
|
+
discussion,
|
|
116
|
+
session.intent,
|
|
117
|
+
session.writerId,
|
|
119
118
|
);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
throw new Error(
|
|
126
|
-
"A transaction record was edited; historical facts are no longer intact",
|
|
127
|
-
);
|
|
128
|
-
const records = trusted
|
|
129
|
-
.map((comment) => decodeRecord(comment.body))
|
|
130
|
-
.filter(Boolean);
|
|
119
|
+
const threads = await readDiscussionThreads(transport, {
|
|
120
|
+
...session,
|
|
121
|
+
intent,
|
|
122
|
+
discussion,
|
|
123
|
+
});
|
|
131
124
|
return {
|
|
132
125
|
discussion,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
...threads,
|
|
127
|
+
...readReleaseDiscussion({
|
|
128
|
+
body: discussion.body,
|
|
129
|
+
records: threads.records,
|
|
130
|
+
}),
|
|
136
131
|
};
|
|
137
132
|
}
|
|
138
133
|
async function append(session, record) {
|
|
139
134
|
if (session.dryRun) return { dryRun: true };
|
|
140
|
-
|
|
141
|
-
record,
|
|
142
|
-
`${record.node}: ${record.status} (attempt ${record.attempt})`,
|
|
143
|
-
);
|
|
135
|
+
let observed;
|
|
144
136
|
const find = async () => {
|
|
145
|
-
|
|
137
|
+
observed = await read(session);
|
|
146
138
|
const existing = observed.comments.find(
|
|
147
139
|
(comment) => decodeRecord(comment.body)?.id === record.id,
|
|
148
140
|
);
|
|
@@ -155,8 +147,13 @@ export function releaseDiscussionStore(
|
|
|
155
147
|
};
|
|
156
148
|
const existing = await find();
|
|
157
149
|
if (existing) return existing;
|
|
150
|
+
const parent = eventParent(observed, record);
|
|
151
|
+
const body = encodeRecord(
|
|
152
|
+
record,
|
|
153
|
+
renderEvent(record, session.intent, observed),
|
|
154
|
+
);
|
|
158
155
|
try {
|
|
159
|
-
await transport.append(session.discussion.id, body);
|
|
156
|
+
await transport.append(session.discussion.id, body, parent);
|
|
160
157
|
} catch (error) {
|
|
161
158
|
return observeAfterUnknown(find, error);
|
|
162
159
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { decodeRecord } from "./envelope.js";
|
|
2
|
+
import { collectDiscussionPages } from "../../providers/github/discussions/transport.js";
|
|
3
|
+
|
|
4
|
+
export function isAttemptRoot(record) {
|
|
5
|
+
return (
|
|
6
|
+
record?.node === "attempt" &&
|
|
7
|
+
record.kind === "progress" &&
|
|
8
|
+
record.status === "running" &&
|
|
9
|
+
record.sequence === 0
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Provider placement is checked before the pure reader reduces event semantics.
|
|
14
|
+
// Historical flat records retain their original representation; new writes always
|
|
15
|
+
// use roots and replies. An explicit intent organization makes placement strict.
|
|
16
|
+
export async function readDiscussionThreads(transport, session) {
|
|
17
|
+
const top = await collectDiscussionPages((after) =>
|
|
18
|
+
transport.comments(session.discussion.id, after),
|
|
19
|
+
);
|
|
20
|
+
const comments = [...top];
|
|
21
|
+
for (const root of top) {
|
|
22
|
+
if (root.replies?.totalCount === 0) continue;
|
|
23
|
+
const replies = await collectDiscussionPages((after) =>
|
|
24
|
+
transport.replies(root.id, after),
|
|
25
|
+
);
|
|
26
|
+
for (const reply of replies) {
|
|
27
|
+
if (reply.replyTo?.id !== root.id)
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Discussion reply parent differs from its queried thread",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
comments.push(...replies);
|
|
33
|
+
if (Buffer.byteLength(JSON.stringify(comments)) > 32 * 1024 * 1024)
|
|
34
|
+
throw new Error("Discussion threads exceed the complete-view byte bound");
|
|
35
|
+
}
|
|
36
|
+
const trusted = comments.filter(
|
|
37
|
+
(comment) => comment.author?.id === session.writerId,
|
|
38
|
+
);
|
|
39
|
+
const records = new Map();
|
|
40
|
+
for (const comment of trusted) {
|
|
41
|
+
const record = decodeRecord(comment.body);
|
|
42
|
+
if (!record) continue;
|
|
43
|
+
if (comment.lastEditedAt)
|
|
44
|
+
throw new Error(
|
|
45
|
+
"A transaction record was edited; historical facts are no longer intact",
|
|
46
|
+
);
|
|
47
|
+
records.set(comment.id, record);
|
|
48
|
+
}
|
|
49
|
+
const roots = new Map();
|
|
50
|
+
for (const comment of trusted) {
|
|
51
|
+
const record = records.get(comment.id);
|
|
52
|
+
if (!isAttemptRoot(record)) continue;
|
|
53
|
+
if (comment.replyTo || roots.has(record.attempt))
|
|
54
|
+
throw new Error("Ambiguous or nested Discussion attempt root");
|
|
55
|
+
roots.set(record.attempt, comment);
|
|
56
|
+
}
|
|
57
|
+
for (const comment of trusted) {
|
|
58
|
+
const record = records.get(comment.id);
|
|
59
|
+
if (!record || isAttemptRoot(record)) continue;
|
|
60
|
+
const root = roots.get(record.attempt);
|
|
61
|
+
if (comment.replyTo) {
|
|
62
|
+
if (!root || comment.replyTo.id !== root.id)
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Transaction event is attached to a different attempt root",
|
|
65
|
+
);
|
|
66
|
+
} else if (session.intent.organization === "attempt-threads/v1") {
|
|
67
|
+
throw new Error("Transaction event requires its attempt root reply");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { comments: trusted, records: [...records.values()], roots };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function eventParent(state, record) {
|
|
74
|
+
const root = state.roots.get(record.attempt);
|
|
75
|
+
if (isAttemptRoot(record)) {
|
|
76
|
+
if (root)
|
|
77
|
+
throw new Error("Attempt root already exists with different content");
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (!root)
|
|
81
|
+
throw new Error("Open the attempt root before appending its events");
|
|
82
|
+
return root.id;
|
|
83
|
+
}
|