@hadden-industries/markdown-quality 1.0.0 → 1.0.2
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/docs/consumer-guide.md +3 -0
- package/docs/support-and-release.md +35 -6
- package/package.json +3 -3
- package/src/formatting.js +8 -4
- package/src/literal-layout.js +79 -0
package/docs/consumer-guide.md
CHANGED
|
@@ -77,6 +77,9 @@ Lint messages describe the violated rule without quoting document content.
|
|
|
77
77
|
These are conservative operational limits; benchmark and platform acceptance remains part of candidate qualification.
|
|
78
78
|
|
|
79
79
|
Formatting compares maintained parsed structure and literals before replacement and verifies convergence.
|
|
80
|
+
Fenced and indented code retain literal whitespace, including whitespace-only lines and trailing spaces; embedded code formatting remains disabled.
|
|
81
|
+
Inline code compares [CommonMark code-span semantics](https://spec.commonmark.org/0.31.2/#code-spans): each line ending means one space, while interior spaces, tabs and other literal characters remain significant.
|
|
82
|
+
Delimiter padding is already handled by the maintained parser; the comparison does not collapse or trim additional whitespace.
|
|
80
83
|
The parsed comparison is an additional backstop, not a universal rendered-equivalence guarantee.
|
|
81
84
|
It conservatively refuses unsupported semantic or literal changes.
|
|
82
85
|
Ordinary file permission bits are preserved; ownership changes, alternate streams, ACL inheritance, arbitrary metadata, and adversarial filesystem races are not promised as preserved.
|
|
@@ -15,9 +15,18 @@ An opt-in compatible feature may use a minor version.
|
|
|
15
15
|
Changed defaults require a new preset major, and a package major when existing invocations change.
|
|
16
16
|
Dependency upgrades are classified from observed corpus and consumer deltas.
|
|
17
17
|
Never overwrite a published version.
|
|
18
|
+
After npm accepts publication, allow up to five minutes for anonymous version metadata and both installer metadata representations to expose the exact approved integrity.
|
|
19
|
+
Only a processing 404 is retried; mismatched integrity and other failures stop delivery.
|
|
20
|
+
Publication is never repeated by this availability check.
|
|
21
|
+
The three bounded waits fit within the publisher's thirty-minute job limit.
|
|
18
22
|
|
|
19
|
-
The current source prepares version 1.0.
|
|
23
|
+
The current source prepares version 1.0.2 under the pilot tag; stable promotion remains gated.
|
|
20
24
|
Stable promotion requires the same qualified tuple on both platforms, public registry readback and unauthenticated installation, both pilot acceptances, and a timed restoration exercise.
|
|
25
|
+
The 1.0.1 corrective candidate restores checking and formatting of literal code lines containing trailing or whitespace-only spaces.
|
|
26
|
+
The original 1.0.1 tuple is published under `pilot` and passed both-platform complete registry qualification in recovery run `37413673170`, without republishing any package.
|
|
27
|
+
The 1.0.2 correction compares inline-code line endings as spaces under CommonMark, retaining meaningful interior whitespace and the strict fenced/indented-code guard.
|
|
28
|
+
This repair changes shipped JavaScript and therefore requires a new coordinated patch version; the unchanged 1.0.0 tuple completed registry qualification separately in recovery run `37409484508`.
|
|
29
|
+
Keep latest promotion blocked until the literal-preservation repair and exact candidate satisfy the same platform, registry and pilot gates.
|
|
21
30
|
Changed/referrer mode and later fleet adoptions remain deferred.
|
|
22
31
|
|
|
23
32
|
Prepare assets explicitly from their frozen manifest.
|
|
@@ -40,8 +49,8 @@ An upstream Cargo lock is a superset of features and development inputs and does
|
|
|
40
49
|
The original upstream license and per-asset repack identity are retained.
|
|
41
50
|
|
|
42
51
|
The owner selected public distribution after npm rejected the private publication with E402.
|
|
43
|
-
All three packages declare public access and share the source candidate version 1.0.
|
|
44
|
-
|
|
52
|
+
All three packages declare public access and share the source candidate version 1.0.2.
|
|
53
|
+
The tuple retains alpha.4's qualified native executable/source/notice bytes and avoids duplicate formatting of byte-identical documents while retaining independent prose checks and preservation/convergence checks.
|
|
45
54
|
The first alpha.3 hosted OWLAPI shadow checks passed correctness but exceeded the accepted 30-second budget on both platforms.
|
|
46
55
|
Alpha.3 and alpha.4 remain immutable historical evidence; every new tuple requires fresh transported-archive, registry, and exact-version pilot qualification before promotion.
|
|
47
56
|
The public alpha.2 archives remain immutable historical delivery evidence and are unqualified for Linux execution.
|
|
@@ -51,9 +60,17 @@ The unpublished private alpha.1 candidate and all rejected attempts remain histo
|
|
|
51
60
|
Direct first publication uses explicit public access; a staged placeholder is unnecessary.
|
|
52
61
|
The manual publisher uses npm 12.2.0 in hosted CI and requires all three npm trusted-publisher mappings for this repository, publish.yml and the npm-publication environment.
|
|
53
62
|
That environment must permit exactly the main branch.
|
|
54
|
-
|
|
63
|
+
Ordinary publication verifies a successful same-source candidate run, all three same-attempt jobs, the approved manifest digest and exact frozen archive bytes.
|
|
64
|
+
Recovery preserves the original candidate's source and archives separately from the current reviewed publishing-control source.
|
|
65
|
+
An admission job without OIDC binds a complete three-package origin map before effects; both platform jobs qualify previously attempted packages before the publisher may run.
|
|
66
|
+
Only a prior planned effect with an explicitly skipped publication step is eligible for first publication; an attempted or unknown outcome must be verified in the registry and cannot become eligible through a 404.
|
|
67
|
+
Each downstream job rehashes its downloaded archives against the admitted manifest.
|
|
68
|
+
The publisher rechecks current main, all three local archive hashes and version absence immediately before each single eligible publication, verifies both native archives before core, and uses only pilot.
|
|
55
69
|
Only the publication job has OIDC issuance permission; no long-lived npm token is supplied.
|
|
56
|
-
Both registry jobs use native npm signature/attestation verification and bind
|
|
70
|
+
Both registry jobs use native npm signature/attestation verification and bind each installed package to its admitted source, workflow, exact publication run and attempt.
|
|
71
|
+
Their explicit non-cancellation and successful-publication condition prevents a skipped recovery ancestor from suppressing final qualification after ordinary publication.
|
|
72
|
+
Successful publication alone does not establish complete registry qualification; retain both actual terminal platform reports.
|
|
73
|
+
Each platform installs and cryptographically verifies its supported native package and core; opposite-platform native archives receive anonymous raw-byte verification, with the combined platform records covering the complete tuple.
|
|
57
74
|
Structural provenance fixtures do not establish cryptographic acceptance.
|
|
58
75
|
The workflow does not promote latest; both pilots and recovery must accept the exact tuple first.
|
|
59
76
|
GitHub artifacts expire after 30 days, so export the complete frozen source, manifest, archives and verification bundles to the retained evidence store before expiry, for the security policy's maintained-lifetime-plus-three-years floor.
|
|
@@ -67,6 +84,17 @@ Qualify root and isolated consumers on both supported platforms from the registr
|
|
|
67
84
|
Do not perform registry publication while rights, publisher authentication, independent assurance, or platform evidence remains missing.
|
|
68
85
|
A partial publication stays unpromoted and receives a new corrective version if bytes change.
|
|
69
86
|
|
|
87
|
+
For unchanged-version recovery, manually dispatch `publish.yml` with the original `candidate-run` and `manifest-sha256`, plus the terminal prior `recovery-run` and exact `recovery-attempt`.
|
|
88
|
+
The original legacy publication can omit `recovery-origins-sha256` only when its source is the original artifact source and its retained manifest matches exactly.
|
|
89
|
+
For a later recovery, independently approve the prior admission map's SHA-256 and pass it as `recovery-origins-sha256`; never infer expected origins from the attestation being verified.
|
|
90
|
+
Retain the actual dispatch inputs, original candidate/manifest, admission artifact, per-package map, provider attempts/jobs, effect outcomes and both registry reports.
|
|
91
|
+
Use a new manual dispatch rather than blindly rerunning a partial publication.
|
|
92
|
+
After known acceptance, availability polling retries metadata 404 or an installer metadata document whose version map has not yet acquired the target version for at most five minutes with bounded requests; it never invokes publication again.
|
|
93
|
+
The version endpoint, abbreviated installer metadata and full installer metadata share that deadline; every visible target must match the approved name, version and integrity.
|
|
94
|
+
Anonymous qualification repeats this admission before installing; malformed metadata, mismatched bytes and access failures are terminal.
|
|
95
|
+
The publication job is bounded to 30 minutes to accommodate three independent visibility waits and native verification; failed or cancelled jobs require evidence reconciliation, not automatic publication retries.
|
|
96
|
+
An unavailable attempted package, mismatched bytes/origin, non-404 registry failure or unresolved live producer stops delivery until its outcome is established.
|
|
97
|
+
|
|
70
98
|
For recovery, retain the exact consumer base revision, old manifest and lock, workflows, selected document preimages, and unrelated-file sentinels.
|
|
71
99
|
Restore only migration-owned changes.
|
|
72
100
|
Verify byte identities and required checks, then record elapsed restoration time.
|
|
@@ -91,7 +119,8 @@ The memory budgets are 512 MiB Windows Job Object committed memory and 512 MiB L
|
|
|
91
119
|
These budgets do not impose OS runtime limits or raise existing package limits.
|
|
92
120
|
Require zero unexpected errors or adjudicated false positives in the window and accepted fixture/probe corpus, retaining expected negative probes separately.
|
|
93
121
|
Rehearse one task-owned pilot restoration within 60 minutes through byte/sentinel readback and incumbent local checks; hosted required-status recovery remains separate acceptance evidence.
|
|
94
|
-
|
|
122
|
+
The alpha.4 pilot cutovers passed both hosted platform windows and scoped restoration; those records do not establish acceptance of a later exact tuple.
|
|
123
|
+
Version 1.0.1 requires fresh per-consumer scope, hosted platform windows, restoration and exact trusted-run acceptance before promotion.
|
|
95
124
|
|
|
96
125
|
Provide AGPL corresponding source and build materials alongside object-code delivery to actual recipients.
|
|
97
126
|
Preserve third-party license texts and attribution.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hadden-industries/markdown-quality",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Shared, bounded Markdown formatting, GFM linting, and local-link checking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"prettier": "3.9.9"
|
|
47
47
|
},
|
|
48
48
|
"optionalDependencies": {
|
|
49
|
-
"@hadden-industries/markdown-quality-win32-x64": "1.0.
|
|
50
|
-
"@hadden-industries/markdown-quality-linux-x64": "1.0.
|
|
49
|
+
"@hadden-industries/markdown-quality-win32-x64": "1.0.2",
|
|
50
|
+
"@hadden-industries/markdown-quality-linux-x64": "1.0.2"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/formatting.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
-
import
|
|
2
|
+
import { formatLayout } from "./literal-layout.js";
|
|
3
3
|
import { parse } from "./analysis.js";
|
|
4
4
|
import { runNative } from "./native-tool.js";
|
|
5
5
|
import { checkProse } from "./prose-diagnostics.js";
|
|
@@ -11,6 +11,10 @@ function semantic(node) {
|
|
|
11
11
|
if (key === "children") value.children = item.map(semantic);
|
|
12
12
|
else if (key === "value" && node.type === "text")
|
|
13
13
|
value.value = item.replace(/\s+/gu, " ");
|
|
14
|
+
// CommonMark code spans turn each line ending into one space. The parser
|
|
15
|
+
// already strips delimiter padding; preserve all other literal characters.
|
|
16
|
+
else if (key === "value" && node.type === "inlineCode")
|
|
17
|
+
value.value = item.replace(/\r\n|\r|\n/gu, " ");
|
|
14
18
|
else value[key] = item;
|
|
15
19
|
}
|
|
16
20
|
return value;
|
|
@@ -30,8 +34,8 @@ export async function formatDocument(text, context, tool) {
|
|
|
30
34
|
tabWidth: context.config.layout.tabWidth,
|
|
31
35
|
plugins: [],
|
|
32
36
|
};
|
|
33
|
-
const layout = await
|
|
34
|
-
const result = await
|
|
37
|
+
const layout = await formatLayout(text, options);
|
|
38
|
+
const result = await formatLayout(runNative(tool, layout), options);
|
|
35
39
|
// Byte identity proves preservation and an observed fixed point of the entire
|
|
36
40
|
// deterministic formatter pipeline. The independent prose check still runs.
|
|
37
41
|
if (result === text)
|
|
@@ -41,7 +45,7 @@ export async function formatDocument(text, context, tool) {
|
|
|
41
45
|
JSON.stringify(semantic(parse(result)))
|
|
42
46
|
)
|
|
43
47
|
fail("PRESERVATION", "Formatting changed parsed meaning or a literal.");
|
|
44
|
-
const second = await
|
|
48
|
+
const second = await formatLayout(runNative(tool, result), options);
|
|
45
49
|
if (result !== second)
|
|
46
50
|
fail("CONVERGENCE", "Formatter pipeline did not converge.");
|
|
47
51
|
return { output: result, diagnostics: checkProse(tool, result) };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import * as prettier from "prettier";
|
|
4
|
+
import { parse } from "./analysis.js";
|
|
5
|
+
import { fail } from "./contracts.js";
|
|
6
|
+
|
|
7
|
+
// embeddedLanguageFormatting: off still allows Prettier to trim code-line
|
|
8
|
+
// whitespace. Keep those lines nonempty until layout finishes, then remove the
|
|
9
|
+
// absent marker before native analysis or the unchanged semantic guard runs.
|
|
10
|
+
export async function formatLayout(text, options) {
|
|
11
|
+
const rows = text.split(/(\r\n|\r|\n)/u);
|
|
12
|
+
const selected = new Set();
|
|
13
|
+
function visit(node, quoteDepth = 0) {
|
|
14
|
+
if (node.type === "blockquote") quoteDepth++;
|
|
15
|
+
if (node.type === "code") {
|
|
16
|
+
// mdast locates actual opening fences at their marker; indented code
|
|
17
|
+
// starts at its indentation, even if its literal text resembles a fence.
|
|
18
|
+
const opening = /^(`{3,}|~{3,})/u.exec(
|
|
19
|
+
text.slice(node.position.start.offset),
|
|
20
|
+
);
|
|
21
|
+
let closing = rows[(node.position.end.line - 1) * 2];
|
|
22
|
+
for (let quote = 0; quote < quoteDepth; quote++) {
|
|
23
|
+
const prefix = /^[ \t]*>[ \t]?/u.exec(closing);
|
|
24
|
+
if (!prefix) {
|
|
25
|
+
closing = "";
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
closing = closing.slice(prefix[0].length);
|
|
29
|
+
}
|
|
30
|
+
closing = closing.replace(/^[ \t]+|[ \t]+$/gu, "");
|
|
31
|
+
const closed =
|
|
32
|
+
opening &&
|
|
33
|
+
closing.length >= opening[1].length &&
|
|
34
|
+
[...closing].every((character) => character === opening[1][0]) &&
|
|
35
|
+
// A fence-shaped line can itself be literal content. The maintained
|
|
36
|
+
// parser omits a real closer from its value; compare source/value line
|
|
37
|
+
// counts rather than inferring valid closing indentation ourselves.
|
|
38
|
+
(node.value === "" ||
|
|
39
|
+
node.position.end.line -
|
|
40
|
+
node.position.start.line -
|
|
41
|
+
(node.position.end.column === 1 ? 1 : 0) >
|
|
42
|
+
node.value.split(/\r\n|\r|\n/u).length);
|
|
43
|
+
// An EOF position at column one names the following empty source row,
|
|
44
|
+
// not another literal line. Marking it would add a newline to the value.
|
|
45
|
+
const endLine =
|
|
46
|
+
node.position.end.line -
|
|
47
|
+
(closed || node.position.end.column === 1 ? 1 : 0);
|
|
48
|
+
for (
|
|
49
|
+
let line = node.position.start.line - (opening ? 0 : 1);
|
|
50
|
+
line < endLine;
|
|
51
|
+
line++
|
|
52
|
+
)
|
|
53
|
+
if (
|
|
54
|
+
rows[line * 2] !== undefined &&
|
|
55
|
+
(rows[line * 2] === "" || /\s$/u.test(rows[line * 2]))
|
|
56
|
+
)
|
|
57
|
+
selected.add(line * 2);
|
|
58
|
+
}
|
|
59
|
+
for (const child of node.children ?? []) visit(child, quoteDepth);
|
|
60
|
+
}
|
|
61
|
+
visit(parse(text));
|
|
62
|
+
if (!selected.size) return prettier.format(text, options);
|
|
63
|
+
let marker;
|
|
64
|
+
for (let code = 0xe000; code < 0xe100; code++) {
|
|
65
|
+
const candidate = String.fromCharCode(code);
|
|
66
|
+
if (!text.includes(candidate)) {
|
|
67
|
+
marker = candidate;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
marker ??= `MQ_LITERAL_${createHash("sha256").update(text).digest("hex")}`;
|
|
72
|
+
if (text.includes(marker))
|
|
73
|
+
fail("PRESERVATION", "Cannot protect literal layout without a collision.");
|
|
74
|
+
for (const line of selected) rows[line] += marker;
|
|
75
|
+
const formatted = await prettier.format(rows.join(""), options);
|
|
76
|
+
if (formatted.split(marker).length - 1 !== selected.size)
|
|
77
|
+
fail("PRESERVATION", "Formatting changed literal layout protection.");
|
|
78
|
+
return formatted.replaceAll(marker, "");
|
|
79
|
+
}
|