Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 111 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ jobs:
with:
name: colors-release-${{ github.sha }}-attempt-${{ github.run_attempt }}
path: ${{ runner.temp }}/node-floor-artifact
- name: packed public runtime on declared Node floor
- name: packed public entrypoints on declared Node floor
env:
ARTIFACT_DIR: ${{ runner.temp }}/node-floor-artifact
run: |
mapfile -t tarballs < <(find "$ARTIFACT_DIR" -type f -name '*.tgz' -print)
test "${#tarballs[@]}" -eq 1
node scripts/verify-package-release.mjs --runtime-smoke "${tarballs[0]}"
node scripts/verify-package-release.mjs --package-smoke "${tarballs[0]}"

msrv:
name: MSRV workspace check
Expand Down Expand Up @@ -241,11 +241,16 @@ jobs:
text=True,
))
packages = {package["name"]: package for package in metadata["packages"]}
consumers = (
direct_core_consumers = (
"labcolors-wasm",
"labcolors-ffi",
"labcolors-conformance",
)
protocol_consumers = (
"labcolors-compiler",
"labcolors-ffi",
"labcolors-conformance",
)
core = packages["labcolors-core"]
if core["features"].get("default") != [
"wcag22-feasibility",
Expand All @@ -269,7 +274,7 @@ jobs:
f"{protocol_core['features']}"
)

for consumer in consumers:
for consumer in direct_core_consumers:
core_dependencies = [
dependency
for dependency in packages[consumer]["dependencies"]
Expand All @@ -285,6 +290,7 @@ jobs:
f"{consumer} direct Core edge must own no optional capability, got "
f"{core_dependency['features']}"
)
for consumer in protocol_consumers:
protocol_dependencies = [
dependency
for dependency in packages[consumer]["dependencies"]
Expand All @@ -305,6 +311,47 @@ jobs:
f"{consumer} resolved the Core-only explicit-domain capability"
)

runtime_dependencies = packages["labcolors-wasm"]["dependencies"]
if any(dependency["name"] == "labcolors-protocol" for dependency in runtime_dependencies):
raise SystemExit("labcolors-wasm must not depend on labcolors-protocol")
compiler_dependencies = packages["labcolors-compiler"]["dependencies"]
if any(
dependency["name"] in {"labcolors-core", "labcolors-wasm"}
for dependency in compiler_dependencies
):
raise SystemExit("labcolors-compiler must be a thin protocol-only adapter")

runtime_tree = subprocess.check_output(
[
"cargo", "tree", "-p", "labcolors-wasm",
"--target", "wasm32-unknown-unknown",
"--edges", "normal", "-e", "features",
],
text=True,
)
for forbidden in (
"labcolors-protocol",
'labcolors-core feature "wcag22-feasibility"',
'labcolors-core feature "wcag22-explicit-feasibility"',
):
if forbidden in runtime_tree:
raise SystemExit(f"runtime role resolved forbidden capability: {forbidden}")

compiler_tree = subprocess.check_output(
[
"cargo", "tree", "-p", "labcolors-compiler",
"--target", "wasm32-unknown-unknown",
"--edges", "normal", "-e", "features",
],
text=True,
)
if "labcolors-wasm" in compiler_tree:
raise SystemExit("compiler role resolved the theme/runtime engine")
if 'labcolors-core feature "wcag22-feasibility"' not in compiler_tree:
raise SystemExit("compiler role lacks protocol-owned wcag22-feasibility")
if 'labcolors-core feature "wcag22-explicit-feasibility"' in compiler_tree:
raise SystemExit("compiler role resolved the Core-only explicit capability")

print("core capability projection: PASS")
PY
- name: reject WCAG22 source-route redirects and proof vacuums
Expand All @@ -321,6 +368,13 @@ jobs:
run: python3 scripts/verify_wcag22_explicit_selection_identity.py --self-test
- name: validate historical and current WCAG22 feasibility benchmark evidence
run: |
sha256sum --check --strict <<'SHA256'
7e9ffcbdd9d5d50fe681f511c34fc5c5dd270e9c475ce23ae56e9776922a3c5e crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v1.json
d8d5c7f3eda834bca9912d835fe3ada13d9dcd5a11cb47a131736716b0b51202 crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v2.json
46ec939523a9aff4f253c4c74e997dfd95812a694b2507fae885ff60244ade3a crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v3.json
3c257c336bc403eee933990fd7188a3b0a6e89d0cbc983aff18846ef76206275 crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v4.json
SHA256

historical_artifact="crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v1.json"
historical_protocol=(
--admit-revision fea99a1ea4148a5a72423c88220655f7f84213fe
Expand Down Expand Up @@ -385,15 +439,40 @@ jobs:
--self-test
)
cleanup_history

v3_artifact="crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v3.json"
v3_snapshot=10c44ef0f4248d0390aa339e81c05a6d5e41996f
v3_protocol=(
--admit-rustc-release 1.96.0
--admit-cargo-release 1.96.0
--admit-rustc-binary-sha256 c5922366bfe3d6d028a65d626f4e629b3adad066995cf0b60c8a4b617bba5ffe
--admit-cargo-binary-sha256 fec239e6b74df873f54ef52912bfcfcc8d8414bc14a7ae1e0be80460bae72841
--admit-benchmark-binary-sha256 6ac07bad81a204ee8fcee8f94a3c445f881d1ca10edaf4cc4a86a5db0b232e3a
--admit-target-triple aarch64-apple-darwin
--admit-target-arch aarch64
--admit-target-os macos
--admit-pointer-width-bits 64
--admit-package-version 0.2.0
--admit-sample-count 5
)
git worktree add --detach "$historical_root" "$v3_snapshot"
(
cd "$historical_root"
python3 scripts/check_wcag22_feasibility_benchmark.py \
"$v3_artifact" "${v3_protocol[@]}" \
--artifact-sha256 46ec939523a9aff4f253c4c74e997dfd95812a694b2507fae885ff60244ade3a \
--self-test
)
cleanup_history
trap - EXIT

current_artifact="crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v3.json"
current_artifact="crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v4.json"
current_protocol=(
--admit-rustc-release 1.96.0
--admit-cargo-release 1.96.0
--admit-rustc-binary-sha256 c5922366bfe3d6d028a65d626f4e629b3adad066995cf0b60c8a4b617bba5ffe
--admit-cargo-binary-sha256 fec239e6b74df873f54ef52912bfcfcc8d8414bc14a7ae1e0be80460bae72841
--admit-benchmark-binary-sha256 6ac07bad81a204ee8fcee8f94a3c445f881d1ca10edaf4cc4a86a5db0b232e3a
--admit-benchmark-binary-sha256 69fe95cea34c845478c0a3c260e3e4459f1bc09d76857b74d5edb50fd923410a
--admit-target-triple aarch64-apple-darwin
--admit-target-arch aarch64
--admit-target-os macos
Expand All @@ -403,7 +482,7 @@ jobs:
)
python3 scripts/check_wcag22_feasibility_benchmark.py \
"$current_artifact" "${current_protocol[@]}" \
--artifact-sha256 46ec939523a9aff4f253c4c74e997dfd95812a694b2507fae885ff60244ade3a \
--artifact-sha256 3c257c336bc403eee933990fd7188a3b0a6e89d0cbc983aff18846ef76206275 \
--self-test

audit:
Expand Down Expand Up @@ -497,45 +576,49 @@ jobs:
run: |
cargo install wasm-pack --version 0.13.1 --locked
echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"
- name: wasm-pack build (release)
# The release bundle: panic=abort, no panic hook, wasm-opt -Oz. This is
# the artifact @labpics/colors ships, so the size measured below is real.
- name: wasm-pack build (runtime + compiler release roles)
# Each execution role is a separate Cargo root and physical artifact;
# building them in separate invocations prevents feature unification.
# Rust error locations otherwise embed the self-hosted runner's mutable
# workspace/CARGO_HOME roots and make identical source hash differently.
run: |
export CARGO_ENCODED_RUSTFLAGS="--remap-path-prefix=$GITHUB_WORKSPACE=/workspace/lab-colors"$'\x1f'"--remap-path-prefix=$CARGO_HOME=/cargo-home"
wasm-pack build crates/labcolors-wasm --release --target web --out-dir ../../packages/colors/pkg --out-name labcolors --locked
wasm-pack build crates/labcolors-compiler --release --target web --out-dir ../../packages/colors/compiler --out-name labcolors_compiler --locked
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: ${{ env.NODE_TOOLCHAIN }}
cache: npm
cache-dependency-path: packages/colors/package-lock.json
- name: "verify committed #296-B canonical whole-call WASM boundary evidence"
# The admitted JSON binds exact deterministic bytes/shapes and the
# reviewed Uint8Array package root to this built Linux x64 WASM. Fresh
# latency/maxRSS/pages remain observations only.
- name: verify committed #296-C1 canonical whole-call compiler evidence
working-directory: packages/colors
run: >-
node bench/wcag22-feasibility-boundary.bench.mjs
--verify
- name: independently fingerprint the exact #296-B WASM
--verify bench/wcag22-feasibility-wasm-boundary-v3.json
- name: independently fingerprint both execution-role WASM artifacts
run: |
bytes="$(wc -c < packages/colors/pkg/labcolors_bg.wasm | tr -d '[:space:]')"
sha256="$(sha256sum packages/colors/pkg/labcolors_bg.wasm | cut -d ' ' -f1)"
echo "canonical candidate raw=${bytes}B sha256=${sha256}"
- name: "upload exact #296-B verified whole-call evidence"
for artifact in \
packages/colors/pkg/labcolors_bg.wasm \
packages/colors/compiler/labcolors_compiler_bg.wasm
do
bytes="$(wc -c < "$artifact" | tr -d '[:space:]')"
sha256="$(sha256sum "$artifact" | cut -d ' ' -f1)"
echo "canonical candidate artifact=${artifact} raw=${bytes}B sha256=${sha256}"
done
sha256sum packages/colors/bench/wcag22-feasibility-wasm-boundary-v3.json
- name: upload exact #296-C1 whole-call evidence
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wcag22-feasibility-wasm-boundary-${{ github.sha }}-attempt-${{ github.run_attempt }}
path: |
packages/colors/bench/wcag22-feasibility-wasm-boundary-v2.json
packages/colors/bench/wcag22-feasibility-wasm-boundary-v3.json
packages/colors/pkg/labcolors_bg.wasm
packages/colors/compiler/labcolors_compiler_bg.wasm
if-no-files-found: error
retention-days: 30
- name: enforce measured WASM raw-byte budget
# Issue #296 owns the current exact Linux x64 measurement and zero-headroom
# ceiling. Issues #284/#295 remain immutable historical inputs.
# gzip is transport diagnostics only.
- name: enforce measured WASM role budgets
# Runtime and compiler have independent exact Linux x64 zero-headroom
# ratchets. gzip remains transport diagnostics only.
run: node scripts/check-wasm-size-budget.mjs
- name: "@labpics/colors: typecheck + runtime tests"
# Now that pkg/ exists (built above), the package's public types resolve
Expand Down Expand Up @@ -690,7 +773,9 @@ jobs:
- name: wasm-pack test (headless chrome)
# Chrome is provisioned above; the parity smoke runs against native
# resolve_set inside the same wasm runtime. D1 default from the chapter.
run: wasm-pack test --headless --chrome --chromedriver "$CHROMEDRIVER_PATH" crates/labcolors-wasm --locked
run: |
wasm-pack test --headless --chrome --chromedriver "$CHROMEDRIVER_PATH" crates/labcolors-wasm --locked
wasm-pack test --headless --chrome --chromedriver "$CHROMEDRIVER_PATH" crates/labcolors-compiler --locked
docs-drift:
name: docs-drift (нейминг-канон)
runs-on: [self-hosted, Linux, X64]
Expand Down
88 changes: 87 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ jobs:
throw new Error(`release artifact rejected: ${message}`);
}

function exactKeys(value, expected, label) {
const actual = value && typeof value === "object" && !Array.isArray(value)
? Object.keys(value).sort()
: [];
if (actual.join(",") !== [...expected].sort().join(",")) {
fail(`${label} has a non-canonical shape`);
}
}

function walk(directory, files = []) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
Expand Down Expand Up @@ -313,13 +322,14 @@ jobs:

const manifest = JSON.parse(readFileSync(manifests[0], "utf8"));
const expectedVersion = expectedTag.slice("colors-v".length);
if (manifest.schemaVersion !== 2) fail(`manifest schema ${manifest.schemaVersion}`);
if (manifest.schemaVersion !== 3) fail(`manifest schema ${manifest.schemaVersion}`);
if (manifest.sourceSha !== expectedSha) {
fail(`manifest sourceSha ${manifest.sourceSha} != ${expectedSha}`);
}
if (manifest.npm !== expectedVersion) {
fail(`manifest npm ${manifest.npm} != tag version ${expectedVersion}`);
}
exactKeys(manifest.artifacts, ["tarball", "wasm", "buildMetadata"], "manifest artifacts");

const tarball = tarballs[0];
const bytes = readFileSync(tarball);
Expand All @@ -335,6 +345,82 @@ jobs:
fail(`tarball sha256 ${digest} != manifest ${evidence.sha256}`);
}

const expectedWasm = [
["runtime", "pkg/labcolors_bg.wasm"],
["compiler", "compiler/labcolors_compiler_bg.wasm"],
];
const wasmEvidence = manifest.artifacts?.wasm;
if (!Array.isArray(wasmEvidence) || wasmEvidence.length !== expectedWasm.length) {
fail("manifest must bind exactly the runtime and compiler WASM roles");
}
for (let index = 0; index < expectedWasm.length; index += 1) {
const [role, path] = expectedWasm[index];
const record = wasmEvidence[index];
const keys = record && typeof record === "object" ? Object.keys(record) : [];
if (keys.join(",") !== "role,path,bytes,sha256") {
fail(`${role} WASM record has a non-canonical shape`);
}
if (record.role !== role || record.path !== path) {
fail(`WASM role ${index} must be ${role} at ${path}`);
}
const packedWasm = execFileSync("tar", ["-xOzf", tarball, `package/${path}`]);
const packedDigest = createHash("sha256").update(packedWasm).digest("hex");
if (record.bytes !== packedWasm.length || record.sha256 !== packedDigest) {
fail(`${role} WASM record does not match the exact tarball bytes`);
}
}

const metadataEvidence = manifest.artifacts.buildMetadata;
exactKeys(metadataEvidence, ["path", "bytes", "sha256"], "build metadata record");
if (metadataEvidence.path !== "build-metadata.json") {
fail(`build metadata path ${metadataEvidence.path}`);
}
const packedMetadata = execFileSync(
"tar",
["-xOzf", tarball, "package/build-metadata.json"],
);
const packedMetadataDigest = createHash("sha256").update(packedMetadata).digest("hex");
if (
metadataEvidence.bytes !== packedMetadata.length ||
metadataEvidence.sha256 !== packedMetadataDigest
) {
fail("build metadata record does not match the exact tarball bytes");
}
const metadata = JSON.parse(packedMetadata.toString("utf8"));
exactKeys(
metadata,
["schemaVersion", "package", "sourceSha", "coreVersion", "conformance", "wasm"],
"packed build metadata",
);
exactKeys(metadata.package, ["name", "version"], "packed build metadata package");
exactKeys(
metadata.conformance,
["packVersion", "packDigest", "manifestSha256", "familySetSha256"],
"packed build metadata conformance",
);
if (
metadata.schemaVersion !== 2 ||
metadata.package.name !== "@labpics/colors" ||
metadata.package.version !== expectedVersion ||
metadata.sourceSha !== expectedSha ||
metadata.coreVersion !== manifest.core ||
metadata.conformance.packVersion !== manifest.conformance?.packVersion ||
metadata.conformance.packDigest !== manifest.conformance?.packDigest ||
metadata.conformance.manifestSha256 !== manifest.conformance?.manifestSha256 ||
metadata.conformance.familySetSha256 !== manifest.conformance?.familySetSha256 ||
!Array.isArray(metadata.wasm) ||
metadata.wasm.length !== wasmEvidence.length
) {
fail("packed build metadata does not bind the release identity and conformance");
}
for (let index = 0; index < wasmEvidence.length; index += 1) {
const metadataWasm = metadata.wasm[index];
exactKeys(metadataWasm, ["role", "path", "bytes", "sha256"], "metadata WASM record");
if (JSON.stringify(metadataWasm) !== JSON.stringify(wasmEvidence[index])) {
fail(`metadata WASM role ${index} differs from the release manifest`);
}
}

const packedPackage = JSON.parse(
execFileSync("tar", ["-xOzf", tarball, "package/package.json"], {
encoding: "utf8",
Expand Down
Loading
Loading