@intentset/publisher 0.1.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/dist/body.d.ts +18 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/body.js +34 -0
- package/dist/body.js.map +1 -0
- package/dist/chunks.d.ts +24 -0
- package/dist/chunks.d.ts.map +1 -0
- package/dist/chunks.js +116 -0
- package/dist/chunks.js.map +1 -0
- package/dist/diagnostic.d.ts +25 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +21 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/generate.d.ts +89 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +157 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/project.d.ts +56 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +187 -0
- package/dist/project.js.map +1 -0
- package/dist/publish.d.ts +42 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +132 -0
- package/dist/publish.js.map +1 -0
- package/dist/references.d.ts +32 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +196 -0
- package/dist/references.js.map +1 -0
- package/dist/request.d.ts +63 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +156 -0
- package/dist/request.js.map +1 -0
- package/dist/review.d.ts +58 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +77 -0
- package/dist/review.js.map +1 -0
- package/dist/yaml.d.ts +18 -0
- package/dist/yaml.d.ts.map +1 -0
- package/dist/yaml.js +90 -0
- package/dist/yaml.js.map +1 -0
- package/package.json +36 -0
- package/src/body.ts +32 -0
- package/src/chunks.ts +132 -0
- package/src/diagnostic.ts +44 -0
- package/src/generate.ts +244 -0
- package/src/index.ts +70 -0
- package/src/project.ts +254 -0
- package/src/publish.ts +177 -0
- package/src/references.ts +226 -0
- package/src/request.ts +239 -0
- package/src/review.ts +121 -0
- package/src/yaml.ts +85 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAa,MAAM,iBAAiB,CAAC;AAEzG,OAAO,EAAE,KAAK,kBAAkB,EAAwB,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,aAAa,CAAC;AAE9D,eAAO,MAAM,iBAAiB,YAC5B,eAAe,EACf,eAAe,EACf,OAAO,EACP,SAAS,EACT,YAAY,EACZ,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,MAAM,EACN,SAAS,EACT,MAAM,EACN,cAAc,CACN,CAAC;AACX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,0HAA0H;AAC1H,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,mGAAmG;IACnG,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,0HAA0H;IAC1H,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,iEAAiE;IACjE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACpC;AAID,4FAA4F;AAC5F,wBAAgB,OAAO,CACrB,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,cAAc,GACtB,UAAU,CAkDZ;AAED,uHAAuH;AACvH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,YAAY,GAC3C,OAAO,CAAC,eAAe,EAAE,eAAe,CAAC,GAAG,IAAI,CAiBlD"}
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audience and release projection (Core §9, catalog P01, P03-P06).
|
|
3
|
+
*
|
|
4
|
+
* Deny by default: an artifact is eligible only when every check passes, and
|
|
5
|
+
* the first that fails is recorded as the reason it was excluded, so a report
|
|
6
|
+
* can count exclusions by kind. The checks, in order:
|
|
7
|
+
*
|
|
8
|
+
* not-requested the request names ids and this is not one of them
|
|
9
|
+
* not-knowledge only knowledge is ever published as a document
|
|
10
|
+
* draft, retired Core §7: neither is current publication
|
|
11
|
+
* visibility outside what the projection admits
|
|
12
|
+
* audience the request's audience is not one the knowledge is written for
|
|
13
|
+
* availability-missing no availability block: fails closed (Core §4, §7)
|
|
14
|
+
* product ... edition the request's value is not in that dimension's list (AND across, OR within)
|
|
15
|
+
* flag the knowledge needs a flag the request does not name (all named flags required)
|
|
16
|
+
* needs-review a source changed since review, a pin is missing, or no reviewer (review.ts)
|
|
17
|
+
*
|
|
18
|
+
* An explicitly requested ID that is not eligible is CORE008. Its message
|
|
19
|
+
* names the reason class, and nothing about the artifact beyond its ID. When
|
|
20
|
+
* the artifact's visibility is outside the projection, or no artifact has the
|
|
21
|
+
* ID, the message is the same in both cases and carries no path: a caller
|
|
22
|
+
* without the authorization cannot learn whether an internal ID exists.
|
|
23
|
+
*/
|
|
24
|
+
import { graphHash } from "@intentset/core";
|
|
25
|
+
import { compareStrings, publicationDiagnostic } from "./diagnostic.js";
|
|
26
|
+
import { admits, checkRequest } from "./request.js";
|
|
27
|
+
import { reviewStatus } from "./review.js";
|
|
28
|
+
export const EXCLUSION_REASONS = [
|
|
29
|
+
"not-requested",
|
|
30
|
+
"not-knowledge",
|
|
31
|
+
"draft",
|
|
32
|
+
"retired",
|
|
33
|
+
"visibility",
|
|
34
|
+
"audience",
|
|
35
|
+
"availability-missing",
|
|
36
|
+
"product",
|
|
37
|
+
"release",
|
|
38
|
+
"role",
|
|
39
|
+
"edition",
|
|
40
|
+
"flag",
|
|
41
|
+
"needs-review",
|
|
42
|
+
];
|
|
43
|
+
const PUBLISHABLE_STATUSES = new Set(["approved", "implemented", "released", "deprecated"]);
|
|
44
|
+
/** Core §9: select what this request may publish, before any text is sent to a renderer. */
|
|
45
|
+
export function project(graph, registries, request, options) {
|
|
46
|
+
const checked = checkRequest(request, graph, registries);
|
|
47
|
+
const diagnostics = [...checked.diagnostics];
|
|
48
|
+
if (graphHash(graph) !== options.snapshot.graphHash) {
|
|
49
|
+
diagnostics.push(publicationDiagnostic({
|
|
50
|
+
code: "PUB001",
|
|
51
|
+
artifact: null,
|
|
52
|
+
path: null,
|
|
53
|
+
message: "The snapshot's graph hash is not the hash of the graph being published.",
|
|
54
|
+
remediation: "Publish from the graph the snapshot names; provenance must identify the exact snapshot (Core §7, §9).",
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
if (checked.request === null || diagnostics.length > 0) {
|
|
58
|
+
return { eligible: [], excluded: [], diagnostics, request: null };
|
|
59
|
+
}
|
|
60
|
+
const req = checked.request;
|
|
61
|
+
const requested = req.ids === undefined ? null : new Set(req.ids);
|
|
62
|
+
const reviews = new Map(options.reviews ?? []);
|
|
63
|
+
const review = (artifact) => {
|
|
64
|
+
let status = reviews.get(artifact.meta.id);
|
|
65
|
+
if (status === undefined) {
|
|
66
|
+
status = reviewStatus(graph, artifact);
|
|
67
|
+
reviews.set(artifact.meta.id, status);
|
|
68
|
+
}
|
|
69
|
+
return status;
|
|
70
|
+
};
|
|
71
|
+
const eligible = [];
|
|
72
|
+
const excluded = [];
|
|
73
|
+
const reasons = new Map();
|
|
74
|
+
for (const id of [...graph.artifacts.keys()].sort(compareStrings)) {
|
|
75
|
+
const artifact = graph.artifacts.get(id);
|
|
76
|
+
const reason = requested !== null && !requested.has(id) ? "not-requested" : exclusionReason(artifact, req, review);
|
|
77
|
+
if (reason === null)
|
|
78
|
+
eligible.push(artifact);
|
|
79
|
+
else {
|
|
80
|
+
excluded.push({ id, reason });
|
|
81
|
+
reasons.set(id, reason);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const id of req.ids ?? []) {
|
|
85
|
+
const reason = reasons.get(id);
|
|
86
|
+
const artifact = graph.artifacts.get(id);
|
|
87
|
+
if (artifact !== undefined && reason === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
diagnostics.push(refusal(id, artifact, reason ?? null, req, artifact === undefined ? null : review(artifact)));
|
|
90
|
+
}
|
|
91
|
+
return { eligible, excluded, diagnostics, request: req };
|
|
92
|
+
}
|
|
93
|
+
/** The first check an artifact fails for this request, or null when it is eligible. Requested-ness is the caller's. */
|
|
94
|
+
export function exclusionReason(artifact, request, review) {
|
|
95
|
+
const { meta } = artifact;
|
|
96
|
+
if (meta.type !== "knowledge")
|
|
97
|
+
return "not-knowledge";
|
|
98
|
+
if (meta.status === "draft")
|
|
99
|
+
return "draft";
|
|
100
|
+
if (meta.status === "retired")
|
|
101
|
+
return "retired";
|
|
102
|
+
if (!PUBLISHABLE_STATUSES.has(meta.status))
|
|
103
|
+
return "draft";
|
|
104
|
+
if (!admits(request, meta.visibility))
|
|
105
|
+
return "visibility";
|
|
106
|
+
if (!meta.audiences.includes(request.audience))
|
|
107
|
+
return "audience";
|
|
108
|
+
const availability = meta.availability;
|
|
109
|
+
if (availability === undefined)
|
|
110
|
+
return "availability-missing";
|
|
111
|
+
if (!availability.products.includes(request.product))
|
|
112
|
+
return "product";
|
|
113
|
+
if (!availability.releases.includes(request.release))
|
|
114
|
+
return "release";
|
|
115
|
+
if (!availability.roles.includes(request.role))
|
|
116
|
+
return "role";
|
|
117
|
+
if (!availability.editions.includes(request.edition))
|
|
118
|
+
return "edition";
|
|
119
|
+
if (!availability.flags.every((flag) => request.flags.includes(flag)))
|
|
120
|
+
return "flag";
|
|
121
|
+
if (review(artifact).status !== "current")
|
|
122
|
+
return "needs-review";
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
/** CORE008 for one requested ID. */
|
|
126
|
+
function refusal(id, artifact, reason, request, review) {
|
|
127
|
+
if (artifact === undefined || !admits(request, artifact.meta.visibility)) {
|
|
128
|
+
return publicationDiagnostic({
|
|
129
|
+
code: "CORE008",
|
|
130
|
+
artifact: id,
|
|
131
|
+
path: null,
|
|
132
|
+
message: `${id} was requested but is not publishable (visibility): no artifact with that ID is visible to a ${request.visibility} projection.`,
|
|
133
|
+
remediation: "Request only knowledge this projection admits; anything else needs a separately authorized projection (Core §9).",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const { meta } = artifact;
|
|
137
|
+
const at = { artifact: id, path: artifact.path };
|
|
138
|
+
const refuse = (field, detail, remediation) => publicationDiagnostic({
|
|
139
|
+
code: "CORE008",
|
|
140
|
+
...at,
|
|
141
|
+
field,
|
|
142
|
+
message: `${id} was requested but is not publishable (${reason}): ${detail}.`,
|
|
143
|
+
remediation,
|
|
144
|
+
});
|
|
145
|
+
switch (reason) {
|
|
146
|
+
case "not-knowledge":
|
|
147
|
+
return refuse("/intentset/type", `it is a ${meta.type}, and only knowledge is published`, "Request the knowledge that explains it; engineering artifacts are never published as documents (Core §9).");
|
|
148
|
+
case "draft":
|
|
149
|
+
return refuse("/intentset/status", `its status is ${meta.status}, and drafts are never published`, "Review the knowledge and set its status to approved or later before publishing it (Core §7, §9).");
|
|
150
|
+
case "retired":
|
|
151
|
+
return refuse("/intentset/status", "it is retired, which is excluded from current publication", "Publish the successor named in replacedBy instead (Core §7).");
|
|
152
|
+
case "audience":
|
|
153
|
+
return refuse("/intentset/audiences", `it is not written for the ${request.audience} audience`, "Request an audience the knowledge lists, or add this one after reviewing the text for it (Core §9).");
|
|
154
|
+
case "availability-missing":
|
|
155
|
+
return refuse("/intentset/availability", "it declares no availability, so publication fails closed", "Add availability with products, releases, roles, editions and flags (Core §7).");
|
|
156
|
+
case "product":
|
|
157
|
+
case "release":
|
|
158
|
+
case "role":
|
|
159
|
+
case "edition":
|
|
160
|
+
return refuse(`/intentset/availability/${reason}s`, `it is not available for ${reason} ${request[reason]}`, `Request a ${reason} the knowledge lists, or extend its availability after review; values match exactly (Core §7).`);
|
|
161
|
+
case "flag": {
|
|
162
|
+
const needed = (meta.availability?.flags ?? [])
|
|
163
|
+
.filter((flag) => !request.flags.includes(flag))
|
|
164
|
+
.sort(compareStrings);
|
|
165
|
+
return refuse("/intentset/availability/flags", `it requires flag${needed.length === 1 ? "" : "s"} ${needed.join(", ")}, which the request does not name`, "Request every flag the knowledge names; all named flags are required (Core §7).");
|
|
166
|
+
}
|
|
167
|
+
default:
|
|
168
|
+
return refuse(review !== null && review.reviewer === null
|
|
169
|
+
? "/intentset/reviewedBy"
|
|
170
|
+
: `/intentset/extensions/intentset.org~1review`, `it needs review because ${describeReview(review)}`, "Review the knowledge against its current sources, then update reviewedBy, reviewedAt and the intentset.org/review pins (Core §9).");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function describeReview(review) {
|
|
174
|
+
if (review === null)
|
|
175
|
+
return "its review could not be read";
|
|
176
|
+
const parts = [];
|
|
177
|
+
if (review.changed.length > 0)
|
|
178
|
+
parts.push(`${review.changed.join(", ")} changed since it was reviewed`);
|
|
179
|
+
if (review.missing.length > 0)
|
|
180
|
+
parts.push(`its review pins nothing for ${review.missing.join(", ")}`);
|
|
181
|
+
if (review.reviewer === null)
|
|
182
|
+
parts.push("it names no reviewer");
|
|
183
|
+
if (review.reviewedAt === null)
|
|
184
|
+
parts.push("it gives no review date");
|
|
185
|
+
return parts.length === 0 ? "its review is not current" : parts.join(", and ");
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAA+D,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAA2B,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAqB,YAAY,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,eAAe;IACf,eAAe;IACf,OAAO;IACP,SAAS;IACT,YAAY;IACZ,UAAU;IACV,sBAAsB;IACtB,SAAS;IACT,SAAS;IACT,MAAM;IACN,SAAS;IACT,MAAM;IACN,cAAc;CACN,CAAC;AA8BX,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;AAE5F,4FAA4F;AAC5F,MAAM,UAAU,OAAO,CACrB,KAAY,EACZ,UAAsB,EACtB,OAA2B,EAC3B,OAAuB;IAEvB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7C,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACpD,WAAW,CAAC,IAAI,CACd,qBAAqB,CAAC;YACpB,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,yEAAyE;YAClF,WAAW,EACT,uGAAuG;SAC1G,CAAC,CACH,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACpE,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC;IAC5B,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAuB,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,CAAC,QAAkB,EAAgB,EAAE;QAClD,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAe,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACnD,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAa,CAAC;QACrD,MAAM,MAAM,GAAG,SAAS,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACnH,IAAI,MAAM,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;aACxC,CAAC;YACJ,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,QAAQ,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QAC7D,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,IAAI,IAAI,EAAE,GAAG,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACjH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAC3D,CAAC;AAED,uHAAuH;AACvH,MAAM,UAAU,eAAe,CAC7B,QAAkB,EAClB,OAA2B,EAC3B,MAA4C;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,eAAe,CAAC;IACtD,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5C,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAChD,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,YAAY,CAAC;IAC3D,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,UAAU,CAAC;IAClE,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,sBAAsB,CAAC;IAC9D,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACvE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACvE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IAC9D,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACvE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IACrF,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC;IACjE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oCAAoC;AACpC,SAAS,OAAO,CACd,EAAU,EACV,QAA8B,EAC9B,MAA8B,EAC9B,OAA2B,EAC3B,MAA2B;IAE3B,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACzE,OAAO,qBAAqB,CAAC;YAC3B,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,GAAG,EAAE,gGAAgG,OAAO,CAAC,UAAU,cAAc;YAC9I,WAAW,EACT,kHAAkH;SACrH,CAAC,CAAC;IACL,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC1B,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,CAAC,KAAyB,EAAE,MAAc,EAAE,WAAmB,EAAE,EAAE,CAChF,qBAAqB,CAAC;QACpB,IAAI,EAAE,SAAS;QACf,GAAG,EAAE;QACL,KAAK;QACL,OAAO,EAAE,GAAG,EAAE,0CAA0C,MAAM,MAAM,MAAM,GAAG;QAC7E,WAAW;KACZ,CAAC,CAAC;IACL,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,eAAe;YAClB,OAAO,MAAM,CACX,iBAAiB,EACjB,WAAW,IAAI,CAAC,IAAI,mCAAmC,EACvD,2GAA2G,CAC5G,CAAC;QACJ,KAAK,OAAO;YACV,OAAO,MAAM,CACX,mBAAmB,EACnB,iBAAiB,IAAI,CAAC,MAAM,kCAAkC,EAC9D,kGAAkG,CACnG,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO,MAAM,CACX,mBAAmB,EACnB,2DAA2D,EAC3D,8DAA8D,CAC/D,CAAC;QACJ,KAAK,UAAU;YACb,OAAO,MAAM,CACX,sBAAsB,EACtB,6BAA6B,OAAO,CAAC,QAAQ,WAAW,EACxD,qGAAqG,CACtG,CAAC;QACJ,KAAK,sBAAsB;YACzB,OAAO,MAAM,CACX,yBAAyB,EACzB,0DAA0D,EAC1D,gFAAgF,CACjF,CAAC;QACJ,KAAK,SAAS,CAAC;QACf,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS;YACZ,OAAO,MAAM,CACX,2BAA2B,MAAM,GAAG,EACpC,2BAA2B,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,EACtD,aAAa,MAAM,gGAAgG,CACpH,CAAC;QACJ,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE,CAAC;iBAC5C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;iBAC/C,IAAI,CAAC,cAAc,CAAC,CAAC;YACxB,OAAO,MAAM,CACX,+BAA+B,EAC/B,mBAAmB,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,mCAAmC,EACzG,iFAAiF,CAClF,CAAC;QACJ,CAAC;QACD;YACE,OAAO,MAAM,CACX,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;gBACzC,CAAC,CAAC,uBAAuB;gBACzB,CAAC,CAAC,6CAA6C,EACjD,2BAA2B,cAAc,CAAC,MAAM,CAAC,EAAE,EACnD,mIAAmI,CACpI,CAAC;IACN,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAA2B;IACjD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,8BAA8B,CAAC;IAC3D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IACxG,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,+BAA+B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtG,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACjE,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACtE,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Diagnostic, type Graph, type Registries } from "@intentset/core";
|
|
2
|
+
import { type GeneratedDocument, type PublicationMeta } from "./generate.ts";
|
|
3
|
+
import { type Snapshot } from "./project.ts";
|
|
4
|
+
import { type IndexedRequest, type PublicationRequest } from "./request.ts";
|
|
5
|
+
import type { ReviewStatus } from "./review.ts";
|
|
6
|
+
/** Why a document that passed the projection was still not published. */
|
|
7
|
+
export declare const REFUSAL_REASONS: readonly ["reference", "title", "directive", "markset"];
|
|
8
|
+
export type RefusalReason = (typeof REFUSAL_REASONS)[number];
|
|
9
|
+
export interface PublishOptions {
|
|
10
|
+
snapshot: Snapshot;
|
|
11
|
+
/** The publication timestamp written into every document; an input, so output is reproducible. */
|
|
12
|
+
publishedAt: string;
|
|
13
|
+
/** Also render each document as a complete HTML page with Markset's default stylesheet inlined. */
|
|
14
|
+
renderHtml?: boolean;
|
|
15
|
+
reviews?: ReadonlyMap<string, ReviewStatus>;
|
|
16
|
+
}
|
|
17
|
+
export interface PublishedDocument extends GeneratedDocument {
|
|
18
|
+
html?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PublicationIndex {
|
|
21
|
+
snapshot: Snapshot;
|
|
22
|
+
/** The request without authorization flags, or null when it was refused. */
|
|
23
|
+
request: IndexedRequest | null;
|
|
24
|
+
published: string[];
|
|
25
|
+
/** Exclusions by reason, keys sorted, zero counts left out. */
|
|
26
|
+
excludedCounts: Record<string, number>;
|
|
27
|
+
}
|
|
28
|
+
export interface PublishResult {
|
|
29
|
+
documents: PublishedDocument[];
|
|
30
|
+
index: PublicationIndex;
|
|
31
|
+
diagnostics: Diagnostic[];
|
|
32
|
+
ok: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function publish(graph: Graph, registries: Registries, request: PublicationRequest, options: PublishOptions): PublishResult;
|
|
35
|
+
/**
|
|
36
|
+
* A complete page through render-html, with the provenance block carried into
|
|
37
|
+
* the head, after the charset, as one `<meta name="intentset-publication">`
|
|
38
|
+
* whose content is the block's canonical JSON, so the HTML keeps what the
|
|
39
|
+
* Markset source keeps.
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderDocument(markset: string, publication: PublicationMeta): string;
|
|
42
|
+
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAoB,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAwB,KAAK,QAAQ,EAAW,MAAM,cAAc,CAAC;AAE5E,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAA0B,MAAM,cAAc,CAAC;AACpG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,yEAAyE;AACzE,eAAO,MAAM,eAAe,YAAI,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAU,CAAC;AACvF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,4EAA4E;IAC5E,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,EAAE,EAAE,OAAO,CAAC;CACb;AAED,wBAAgB,OAAO,CACrB,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,cAAc,GACtB,aAAa,CAsEf;AAUD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,MAAM,CAOpF"}
|
package/dist/publish.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publication (Core §9): project, check references, generate, render.
|
|
3
|
+
*
|
|
4
|
+
* canonical files -> validated graph -> exact snapshot
|
|
5
|
+
* -> authorized audience projection (project.ts)
|
|
6
|
+
* -> reviewed knowledge (review.ts)
|
|
7
|
+
* -> references and titles checked (references.ts)
|
|
8
|
+
* -> generated Markset (generate.ts) -> HTML
|
|
9
|
+
*
|
|
10
|
+
* Selection happens before any text reaches a renderer. A document whose
|
|
11
|
+
* references, titles or Markset fail is not emitted. Links are checked against
|
|
12
|
+
* what is actually being published, so a document that links to one refused
|
|
13
|
+
* here is refused in turn; this repeats until nothing more is refused.
|
|
14
|
+
*
|
|
15
|
+
* The index is what a customer-facing tool may read (Core §10): the snapshot,
|
|
16
|
+
* the request without its authorization flags, the published IDs, and the
|
|
17
|
+
* number of artifacts excluded for each reason. It names nothing that was not
|
|
18
|
+
* published.
|
|
19
|
+
*
|
|
20
|
+
* Deterministic: the same graph, request, snapshot and `publishedAt` give the
|
|
21
|
+
* same bytes.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { canonicalJson, hasErrors, sortDiagnostics, } from "@intentset/core";
|
|
25
|
+
import { parseDocument } from "@markset-lang/parser";
|
|
26
|
+
import { defaultStylesheetPath, renderPage } from "@markset-lang/render-html";
|
|
27
|
+
import { compareStrings } from "./diagnostic.js";
|
|
28
|
+
import { generateDocument } from "./generate.js";
|
|
29
|
+
import { project } from "./project.js";
|
|
30
|
+
import { checkReferences, checkTitles } from "./references.js";
|
|
31
|
+
import { admits, indexedRequest } from "./request.js";
|
|
32
|
+
/** Why a document that passed the projection was still not published. */
|
|
33
|
+
export const REFUSAL_REASONS = ["reference", "title", "directive", "markset"];
|
|
34
|
+
export function publish(graph, registries, request, options) {
|
|
35
|
+
const projection = project(graph, registries, request, { snapshot: options.snapshot, reviews: options.reviews });
|
|
36
|
+
const diagnostics = [...projection.diagnostics];
|
|
37
|
+
const counts = new Map();
|
|
38
|
+
const count = (reason) => counts.set(reason, (counts.get(reason) ?? 0) + 1);
|
|
39
|
+
// Only what the projection could see is counted: a customer index that said
|
|
40
|
+
// "not-knowledge: 12" would tell its reader how many internal records exist.
|
|
41
|
+
// A refused request admits nothing, so it counts nothing (invariant 4).
|
|
42
|
+
for (const exclusion of projection.excluded) {
|
|
43
|
+
const visibility = graph.artifacts.get(exclusion.id)?.meta.visibility;
|
|
44
|
+
if (projection.request !== null && visibility !== undefined && admits(projection.request, visibility)) {
|
|
45
|
+
count(exclusion.reason);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const req = projection.request;
|
|
49
|
+
const documents = [];
|
|
50
|
+
if (req !== null) {
|
|
51
|
+
const candidates = projection.eligible;
|
|
52
|
+
const allowedTitles = new Set(candidates.map((artifact) => artifact.meta.title.trim()));
|
|
53
|
+
const refused = new Map();
|
|
54
|
+
const generated = new Map();
|
|
55
|
+
for (const artifact of candidates) {
|
|
56
|
+
const titles = checkTitles(artifact, graph, req, allowedTitles);
|
|
57
|
+
const result = generateDocument(graph, artifact, req, options);
|
|
58
|
+
diagnostics.push(...titles, ...result.diagnostics);
|
|
59
|
+
if (result.document !== null)
|
|
60
|
+
generated.set(artifact.meta.id, result.document);
|
|
61
|
+
if (titles.length > 0)
|
|
62
|
+
refused.set(artifact.meta.id, "title");
|
|
63
|
+
else if (result.diagnostics.some((d) => d.code === "PUB004"))
|
|
64
|
+
refused.set(artifact.meta.id, "directive");
|
|
65
|
+
else if (result.document === null)
|
|
66
|
+
refused.set(artifact.meta.id, "markset");
|
|
67
|
+
}
|
|
68
|
+
let published = new Set(candidates.map((artifact) => artifact.meta.id).filter((id) => !refused.has(id)));
|
|
69
|
+
let round = candidates;
|
|
70
|
+
while (round.length > 0) {
|
|
71
|
+
const failed = [];
|
|
72
|
+
for (const artifact of round) {
|
|
73
|
+
const found = checkReferences(artifact, graph, req, published);
|
|
74
|
+
if (found.length === 0)
|
|
75
|
+
continue;
|
|
76
|
+
diagnostics.push(...found);
|
|
77
|
+
failed.push(artifact.meta.id);
|
|
78
|
+
refused.set(artifact.meta.id, "reference");
|
|
79
|
+
}
|
|
80
|
+
published = new Set([...published].filter((id) => !failed.includes(id)));
|
|
81
|
+
round = failed.length === 0 ? [] : candidates.filter((artifact) => published.has(artifact.meta.id));
|
|
82
|
+
}
|
|
83
|
+
for (const reason of refused.values())
|
|
84
|
+
count(reason);
|
|
85
|
+
for (const id of [...published].sort(compareStrings)) {
|
|
86
|
+
const document = { ...generated.get(id) };
|
|
87
|
+
if (options.renderHtml === true)
|
|
88
|
+
document.html = renderDocument(document.markset, document.publication);
|
|
89
|
+
documents.push(document);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const excludedCounts = {};
|
|
93
|
+
for (const reason of [...counts.keys()].sort(compareStrings))
|
|
94
|
+
excludedCounts[reason] = counts.get(reason);
|
|
95
|
+
const sorted = sortDiagnostics(diagnostics);
|
|
96
|
+
return {
|
|
97
|
+
documents,
|
|
98
|
+
index: {
|
|
99
|
+
snapshot: { commit: options.snapshot.commit, graphHash: options.snapshot.graphHash },
|
|
100
|
+
request: req === null ? null : indexedRequest(req),
|
|
101
|
+
published: documents.map((document) => document.id),
|
|
102
|
+
excludedCounts,
|
|
103
|
+
},
|
|
104
|
+
diagnostics: sorted,
|
|
105
|
+
ok: !hasErrors(sorted),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
let stylesheet = null;
|
|
109
|
+
/** Markset's default stylesheet, read once. */
|
|
110
|
+
function defaultStylesheet() {
|
|
111
|
+
stylesheet ??= readFileSync(defaultStylesheetPath, "utf8");
|
|
112
|
+
return stylesheet;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A complete page through render-html, with the provenance block carried into
|
|
116
|
+
* the head, after the charset, as one `<meta name="intentset-publication">`
|
|
117
|
+
* whose content is the block's canonical JSON, so the HTML keeps what the
|
|
118
|
+
* Markset source keeps.
|
|
119
|
+
*/
|
|
120
|
+
export function renderDocument(markset, publication) {
|
|
121
|
+
const page = renderPage(parseDocument(markset).ast, { stylesheet: { inline: defaultStylesheet() } });
|
|
122
|
+
const meta = `<meta name="intentset-publication" content="${escapeAttribute(canonicalJson(publication))}">\n`;
|
|
123
|
+
const anchor = '<meta charset="utf-8">\n';
|
|
124
|
+
const at = page.indexOf(anchor);
|
|
125
|
+
if (at === -1)
|
|
126
|
+
throw new Error("render-html returned a page without a charset declaration");
|
|
127
|
+
return page.slice(0, at + anchor.length) + meta + page.slice(at + anchor.length);
|
|
128
|
+
}
|
|
129
|
+
function escapeAttribute(value) {
|
|
130
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Artifact, type Diagnostic, type Graph } from "@intentset/core";
|
|
2
|
+
import { type PublicationRequest } from "./request.ts";
|
|
3
|
+
export type ReferenceKind = "id" | "link" | "path";
|
|
4
|
+
export interface Reference {
|
|
5
|
+
/** The referenced artifact's ID. */
|
|
6
|
+
id: string;
|
|
7
|
+
via: ReferenceKind;
|
|
8
|
+
/** 1-based line in the body. */
|
|
9
|
+
line: number;
|
|
10
|
+
}
|
|
11
|
+
/** Every reference from a knowledge body to another artifact in the graph, sorted by ID, kind, line. */
|
|
12
|
+
export declare function findReferences(artifact: Artifact, graph: Graph): Reference[];
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a link destination against the linking document to a repository
|
|
15
|
+
* path. Null for anything that is not a repository file: a URL with a scheme,
|
|
16
|
+
* a protocol-relative URL, a fragment, or a path that leaves the repository.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveTarget(url: string, fromPath: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* CORE008 for each referenced artifact this projection may not show: one
|
|
21
|
+
* diagnostic per referenced ID, at the first failing reference's line.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkReferences(artifact: Artifact, graph: Graph, request: PublicationRequest, published: ReadonlySet<string>): Diagnostic[];
|
|
24
|
+
/**
|
|
25
|
+
* PUB002 for each artifact outside the projection whose title (three or more
|
|
26
|
+
* words) the body contains, unless that title is in `allowedTitles` (the
|
|
27
|
+
* document's own and those of what is being published).
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkTitles(artifact: Artifact, graph: Graph, request: PublicationRequest, allowedTitles: ReadonlySet<string>): Diagnostic[];
|
|
30
|
+
/** The title as a case-sensitive pattern: any whitespace between words, word-bounded at either end that is a word character. */
|
|
31
|
+
export declare function titlePattern(title: string): RegExp;
|
|
32
|
+
//# sourceMappingURL=references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAc,MAAM,iBAAiB,CAAC;AAIzF,OAAO,EAAE,KAAK,kBAAkB,EAAU,MAAM,cAAc,CAAC;AAE/D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,aAAa,CAAC;IACnB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,wGAAwG;AACxG,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,EAAE,CAqD5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB1E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,UAAU,EAAE,CAgCd;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,UAAU,EAAE,CAuBd;AAED,gIAAgI;AAChI,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* References out of a knowledge body (Core §9, catalog P02).
|
|
3
|
+
*
|
|
4
|
+
* A published document may refer only to what its projection may show. Three
|
|
5
|
+
* kinds of reference are found:
|
|
6
|
+
*
|
|
7
|
+
* id a bare artifact ID in the text, word-bounded, code included
|
|
8
|
+
* link a Markdown link, image or definition, or an href/src in raw HTML,
|
|
9
|
+
* whose destination resolves to an artifact's file
|
|
10
|
+
* path an artifact's repository path written out in the text
|
|
11
|
+
*
|
|
12
|
+
* An ID reference fails when the artifact's visibility is outside the
|
|
13
|
+
* projection: the ID itself is then something this audience is not shown. A
|
|
14
|
+
* link or path reference fails when the artifact is not published in this
|
|
15
|
+
* projection, since the reader would be sent to, or told of, a canonical
|
|
16
|
+
* file. The product the request names is not a leak when mentioned by ID.
|
|
17
|
+
*
|
|
18
|
+
* Separately, the text may not contain the title of an artifact the
|
|
19
|
+
* projection does not admit (three or more words, case-sensitive, any run of
|
|
20
|
+
* whitespace between words), unless that title is also the title of
|
|
21
|
+
* something being published. That is PUB002.
|
|
22
|
+
*
|
|
23
|
+
* Every message names the referencing document and the referenced ID, and
|
|
24
|
+
* never the referenced artifact's title or path.
|
|
25
|
+
*/
|
|
26
|
+
import { posix } from "node:path";
|
|
27
|
+
import { ID_PATTERN } from "@intentset/core";
|
|
28
|
+
import { parseDocument } from "@markset-lang/parser";
|
|
29
|
+
import { bodyLineOffset, fileLocation, lineAt } from "./body.js";
|
|
30
|
+
import { compareStrings, publicationDiagnostic } from "./diagnostic.js";
|
|
31
|
+
import { admits } from "./request.js";
|
|
32
|
+
/** ID_PATTERN, unanchored and bounded so it does not match inside a longer token. */
|
|
33
|
+
const VIA_WORDS = { id: "ID", link: "link", path: "path" };
|
|
34
|
+
/** "ID", "ID and link", "ID, link and path". */
|
|
35
|
+
function listing(words) {
|
|
36
|
+
return words.length < 2 ? words.join("") : `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}`;
|
|
37
|
+
}
|
|
38
|
+
const ID_IN_TEXT = new RegExp(`(?<![A-Za-z0-9_-])${ID_PATTERN.source.slice(1, -1)}(?![A-Za-z0-9_-])`, "g");
|
|
39
|
+
const HTML_TARGET = /\b(?:href|src)\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
40
|
+
/** Every reference from a knowledge body to another artifact in the graph, sorted by ID, kind, line. */
|
|
41
|
+
export function findReferences(artifact, graph) {
|
|
42
|
+
const self = artifact.meta.id;
|
|
43
|
+
const body = artifact.body;
|
|
44
|
+
const found = [];
|
|
45
|
+
const add = (id, via, line) => {
|
|
46
|
+
if (id !== self && graph.artifacts.has(id))
|
|
47
|
+
found.push({ id, via, line });
|
|
48
|
+
};
|
|
49
|
+
for (const match of body.matchAll(ID_IN_TEXT))
|
|
50
|
+
add(match[0], "id", lineAt(body, match.index));
|
|
51
|
+
const byPath = new Map();
|
|
52
|
+
for (const [id, other] of graph.artifacts)
|
|
53
|
+
byPath.set(other.path, id);
|
|
54
|
+
const visit = (node) => {
|
|
55
|
+
if (typeof node !== "object" || node === null)
|
|
56
|
+
return;
|
|
57
|
+
const n = node;
|
|
58
|
+
const line = n.position?.start.line ?? 1;
|
|
59
|
+
const targets = [];
|
|
60
|
+
if ((n.type === "link" || n.type === "image" || n.type === "definition") && typeof n.url === "string")
|
|
61
|
+
targets.push(n.url);
|
|
62
|
+
if (n.type === "html" && typeof n.value === "string") {
|
|
63
|
+
for (const match of n.value.matchAll(HTML_TARGET))
|
|
64
|
+
targets.push(match[1] ?? match[2] ?? "");
|
|
65
|
+
}
|
|
66
|
+
for (const url of targets) {
|
|
67
|
+
const path = resolveTarget(url, artifact.path);
|
|
68
|
+
const id = path === null ? undefined : byPath.get(path);
|
|
69
|
+
if (id !== undefined)
|
|
70
|
+
add(id, "link", line);
|
|
71
|
+
}
|
|
72
|
+
for (const child of n.children ?? [])
|
|
73
|
+
visit(child);
|
|
74
|
+
};
|
|
75
|
+
visit(parseDocument(body).ast);
|
|
76
|
+
for (const [id, other] of graph.artifacts) {
|
|
77
|
+
let from = 0;
|
|
78
|
+
for (;;) {
|
|
79
|
+
const index = body.indexOf(other.path, from);
|
|
80
|
+
if (index === -1)
|
|
81
|
+
break;
|
|
82
|
+
const before = index === 0 ? "" : body[index - 1];
|
|
83
|
+
const after = body[index + other.path.length] ?? "";
|
|
84
|
+
if (!/[A-Za-z0-9_./-]/.test(before) && !/[A-Za-z0-9_/-]/.test(after)) {
|
|
85
|
+
add(id, "path", lineAt(body, index));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
from = index + 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return found.sort((a, b) => compareStrings(a.id, b.id) || compareStrings(a.via, b.via) || a.line - b.line);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a link destination against the linking document to a repository
|
|
95
|
+
* path. Null for anything that is not a repository file: a URL with a scheme,
|
|
96
|
+
* a protocol-relative URL, a fragment, or a path that leaves the repository.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveTarget(url, fromPath) {
|
|
99
|
+
const trimmed = url.trim();
|
|
100
|
+
if (trimmed === "" ||
|
|
101
|
+
trimmed.startsWith("#") ||
|
|
102
|
+
trimmed.startsWith("//") ||
|
|
103
|
+
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(trimmed)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
let target = trimmed.split("#")[0].split("?")[0];
|
|
107
|
+
try {
|
|
108
|
+
target = decodeURIComponent(target);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// A malformed escape is compared as written.
|
|
112
|
+
}
|
|
113
|
+
if (target === "")
|
|
114
|
+
return null;
|
|
115
|
+
const joined = target.startsWith("/") ? target.replace(/^\/+/, "") : posix.join(posix.dirname(fromPath), target);
|
|
116
|
+
const normal = posix.normalize(joined);
|
|
117
|
+
if (normal === ".." || normal.startsWith("../"))
|
|
118
|
+
return null;
|
|
119
|
+
return normal.replace(/^\.\//, "");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* CORE008 for each referenced artifact this projection may not show: one
|
|
123
|
+
* diagnostic per referenced ID, at the first failing reference's line.
|
|
124
|
+
*/
|
|
125
|
+
export function checkReferences(artifact, graph, request, published) {
|
|
126
|
+
const offset = bodyLineOffset(artifact);
|
|
127
|
+
const failing = new Map();
|
|
128
|
+
for (const ref of findReferences(artifact, graph)) {
|
|
129
|
+
const target = graph.artifacts.get(ref.id);
|
|
130
|
+
const fails = ref.via === "id"
|
|
131
|
+
? ref.id !== request.product && !admits(request, target.meta.visibility)
|
|
132
|
+
: !published.has(ref.id);
|
|
133
|
+
if (!fails)
|
|
134
|
+
continue;
|
|
135
|
+
const list = failing.get(ref.id);
|
|
136
|
+
if (list === undefined)
|
|
137
|
+
failing.set(ref.id, [ref]);
|
|
138
|
+
else
|
|
139
|
+
list.push(ref);
|
|
140
|
+
}
|
|
141
|
+
const diagnostics = [];
|
|
142
|
+
for (const id of [...failing.keys()].sort(compareStrings)) {
|
|
143
|
+
const refs = failing.get(id);
|
|
144
|
+
const vias = [...new Set(refs.map((ref) => VIA_WORDS[ref.via]))];
|
|
145
|
+
const first = Math.min(...refs.map((ref) => ref.line));
|
|
146
|
+
diagnostics.push(publicationDiagnostic({
|
|
147
|
+
code: "CORE008",
|
|
148
|
+
artifact: artifact.meta.id,
|
|
149
|
+
path: artifact.path,
|
|
150
|
+
location: fileLocation(offset, first),
|
|
151
|
+
message: `${artifact.meta.id} refers to ${id} by ${listing(vias)}, which this ${request.visibility} projection does not publish.`,
|
|
152
|
+
remediation: "Remove the reference, or replace it with reviewed wording safe for this audience; a published document may refer only to what its projection shows (Core §9).",
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
return diagnostics;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* PUB002 for each artifact outside the projection whose title (three or more
|
|
159
|
+
* words) the body contains, unless that title is in `allowedTitles` (the
|
|
160
|
+
* document's own and those of what is being published).
|
|
161
|
+
*/
|
|
162
|
+
export function checkTitles(artifact, graph, request, allowedTitles) {
|
|
163
|
+
const offset = bodyLineOffset(artifact);
|
|
164
|
+
const diagnostics = [];
|
|
165
|
+
for (const id of [...graph.artifacts.keys()].sort(compareStrings)) {
|
|
166
|
+
const other = graph.artifacts.get(id);
|
|
167
|
+
if (id === artifact.meta.id || admits(request, other.meta.visibility))
|
|
168
|
+
continue;
|
|
169
|
+
const title = other.meta.title.trim();
|
|
170
|
+
if (title.split(/\s+/).length < 3 || allowedTitles.has(title))
|
|
171
|
+
continue;
|
|
172
|
+
const match = titlePattern(title).exec(artifact.body);
|
|
173
|
+
if (match === null)
|
|
174
|
+
continue;
|
|
175
|
+
diagnostics.push(publicationDiagnostic({
|
|
176
|
+
code: "PUB002",
|
|
177
|
+
artifact: artifact.meta.id,
|
|
178
|
+
path: artifact.path,
|
|
179
|
+
location: fileLocation(offset, lineAt(artifact.body, match.index)),
|
|
180
|
+
message: `${artifact.meta.id} contains the title of ${id}, which this ${request.visibility} projection does not admit.`,
|
|
181
|
+
remediation: "Reword the guidance so it does not repeat the title of an artifact this audience may not see (Core §9).",
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
return diagnostics;
|
|
185
|
+
}
|
|
186
|
+
/** The title as a case-sensitive pattern: any whitespace between words, word-bounded at either end that is a word character. */
|
|
187
|
+
export function titlePattern(title) {
|
|
188
|
+
const words = title.trim().split(/\s+/).map(escapeRegExp);
|
|
189
|
+
const start = /^[\p{L}\p{N}_]/u.test(title.trim()) ? "(?<![\\p{L}\\p{N}_])" : "";
|
|
190
|
+
const end = /[\p{L}\p{N}_]$/u.test(title.trim()) ? "(?![\\p{L}\\p{N}_])" : "";
|
|
191
|
+
return new RegExp(`${start}${words.join("\\s+")}${end}`, "u");
|
|
192
|
+
}
|
|
193
|
+
function escapeRegExp(text) {
|
|
194
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=references.js.map
|