From 9c6b5c581efe5bef1b3c18661e90e73a3ae636cd Mon Sep 17 00:00:00 2001
From: Todd Wolfe
Date: Thu, 8 Oct 2026 21:56:19 -0400
Subject: [PATCH] Show equal practical v1 integrations across all five root
demos
---
.github/workflows/pages.yml | 2 +-
README.md | 6 +-
app-1.0.0.js | 126 ++++++
dist/browser.js | 79 +++-
index.html | 13 +-
siblings-1.0.0.js | 511 +++++++++++++++++++++++++
style-54d303f2.css | 25 ++
test/site.test.js | 4 +-
test/value.test.js | 36 ++
value-1.0.0.js | 423 ++++++++++++++++++++
vendor/doggo-log/index.js | 28 +-
vendor/doggo-log/redaction.js | 73 ++++
vendor/error-translator/diagnostics.js | 79 ++++
vendor/error-translator/index.js | 1 +
vendor/excuse-js/incident.js | 83 ++++
vendor/excuse-js/index.js | 1 +
vendor/ship-it-meter/index.js | 5 +
vendor/ship-it-meter/reports.js | 262 +++++++++++++
18 files changed, 1739 insertions(+), 18 deletions(-)
create mode 100644 app-1.0.0.js
create mode 100644 siblings-1.0.0.js
create mode 100644 test/value.test.js
create mode 100644 value-1.0.0.js
create mode 100644 vendor/doggo-log/redaction.js
create mode 100644 vendor/error-translator/diagnostics.js
create mode 100644 vendor/excuse-js/incident.js
create mode 100644 vendor/ship-it-meter/reports.js
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 0aa09cc..a31c161 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 24
- - run: node --check app-0.4.0.js && node --check siblings-0.4.0.js && node --check candidate/redirect.js
+ - run: node --check app-1.0.0.js && node --check siblings-1.0.0.js && node --check value-1.0.0.js && node --check candidate/redirect.js
- run: node --test test/*.test.js
deploy:
if: github.event_name != 'pull_request'
diff --git a/README.md b/README.md
index 189a8d0..0d6f69d 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Public interactive demo: https://deployanyway.github.io/
-Static HTML, CSS and browser ES modules. No analytics, accounts or backend. Inputs stay in the browser. The five demos use vendored 0.4.0 package source; the dog logger uses a single-string `node:util` adapter because the form does not accept formatting arguments. This adapter is not a replacement for the npm library's full Node formatting behavior.
+Static HTML, CSS and browser ES modules. No analytics, accounts or backend. Inputs stay in the browser. The five demos use vendored 1.0.0 package source; the dog logger uses a single-string `node:util` adapter because the form does not accept formatting arguments. This adapter is not a replacement for the npm library's full Node formatting behavior.
## Develop
@@ -25,3 +25,7 @@ The organization palette and home badge follow the supplied husky artwork. Dalla
## License
MIT. Vendored package licenses are included in their directories.
+
+## Practical v1 integrations
+
+Each tool keeps its playful library and adds an equally visible developer-value panel: measured build summaries, Error cause diagnostics, accountable incident drafts, context redaction plus a runnable Node async-scope example, and report-backed release policies. The browser runs actual pure package APIs. AsyncLocalStorage, file reading, CI report collection and process exit preservation run in Node; examples identify those boundaries. Demo receipt data is labeled sample data, never claimed as real CI evidence.
diff --git a/app-1.0.0.js b/app-1.0.0.js
new file mode 100644
index 0000000..c7e6571
--- /dev/null
+++ b/app-1.0.0.js
@@ -0,0 +1,126 @@
+import {
+ renderBro,
+ listCharacters,
+ moods,
+ listThemes,
+ broMessage,
+ messageCategories,
+ messagePresets,
+} from "./dist/browser.js";
+
+const element = (id) => document.getElementById(id);
+for (const [id, names, selected] of [
+ ["character", listCharacters(), "husky"],
+ ["mood", moods(), "sarcastic"],
+ ["theme", listThemes(), "classic"],
+]) {
+ for (const name of names) {
+ const option = document.createElement("option");
+ option.value = option.textContent = name;
+ option.selected = name === selected;
+ element(id).append(option);
+ }
+}
+const presetLabel = document.createElement("label");
+presetLabel.textContent = "Message source";
+const presetSelect = document.createElement("select");
+presetSelect.id = "bro-preset";
+for (const value of ["Your message", ...messageCategories()]) {
+ const option = document.createElement("option");
+ option.value = option.textContent = value;
+ presetSelect.append(option);
+}
+presetLabel.append(presetSelect);
+element("controls").prepend(presetLabel);
+const presetList = document.createElement("pre");
+presetList.className = "preset-library";
+presetList.setAttribute("aria-label", "Message preset library");
+element("controls").after(presetList);
+function render() {
+ try {
+ const random = element("bro-selection").value === "random";
+ for (const id of ["character", "mood", "theme"])
+ element(id).disabled = random;
+ const preset = element("bro-preset").value;
+ const generated = preset !== "Your message";
+ element("message").disabled = generated;
+ element("bro-seed").disabled = !random && !generated;
+ const text = generated
+ ? broMessage(preset, { seed: element("bro-seed").value })
+ : element("message").value;
+ presetList.hidden = !generated;
+ presetList.textContent = generated
+ ? "All six " +
+ preset +
+ " presets:\n" +
+ messagePresets(preset)
+ .map((line, i) => i + 1 + ". " + line)
+ .join("\n")
+ : "";
+ const result = renderBro({
+ text,
+ character: random ? undefined : element("character").value,
+ mood: random ? undefined : element("mood").value,
+ theme: random ? undefined : element("theme").value,
+ mode: element("mode").value,
+ width: Number(element("width").value),
+ wrap: element("bro-wrap").checked,
+ ...(element("bro-layout").value === "plain"
+ ? { layout: "plain" }
+ : element("bro-layout").value === "box"
+ ? { box: true }
+ : {}),
+ ...(random ? { random: true, seed: element("bro-seed").value } : {}),
+ });
+ element("output").textContent =
+ element("bro-format").value === "json"
+ ? JSON.stringify(result, null, 2)
+ : result.rendered;
+ const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
+ element("bro-command").textContent =
+ `npx @deployanyway/bro-say@1.0.0 ${generated ? `--preset ${preset}` : quote(text)} --width ${element("width").value}` +
+ (random
+ ? ` --random --seed ${quote(element("bro-seed").value)}`
+ : `${generated ? ` --seed ${quote(element("bro-seed").value)}` : ""} --character ${element("character").value} --mood ${element("mood").value} --theme ${element("theme").value}`) +
+ (element("mode").value === "think" ? " --think" : "") +
+ (element("bro-format").value === "json" ? " --json" : "") +
+ (!element("bro-wrap").checked ? " --no-wrap" : "") +
+ (element("bro-layout").value === "plain"
+ ? " --plain"
+ : element("bro-layout").value === "box"
+ ? " --box"
+ : "");
+ element("status").textContent = "";
+ element("copy").disabled = false;
+ element("bro-copy-command").disabled = false;
+ } catch (error) {
+ element("status").textContent = error.message;
+ element("output").textContent = "";
+ element("copy").disabled = true;
+ element("bro-copy-command").disabled = true;
+ element("bro-command").textContent = "";
+ }
+}
+element("controls").addEventListener("input", render);
+element("controls").addEventListener("submit", (event) =>
+ event.preventDefault(),
+);
+element("copy").addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.writeText(element("output").textContent);
+ element("status").textContent = "Copied. Ship the message responsibly.";
+ } catch {
+ element("status").textContent =
+ "Clipboard unavailable. Select and copy the output manually.";
+ }
+});
+render();
+
+element("bro-copy-command").addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.writeText(element("bro-command").textContent);
+ element("status").textContent = "Command copied.";
+ } catch {
+ element("status").textContent = "Select and copy the command manually.";
+ }
+});
diff --git a/dist/browser.js b/dist/browser.js
index 4f66eb5..d1e2796 100644
--- a/dist/browser.js
+++ b/dist/browser.js
@@ -1495,6 +1495,81 @@ function broMessage(category = "deployment", options = {}) {
"message:" + category.trim().toLowerCase()
);
}
+
+// src/build-summary.js
+var clean = (value) => Array.from(value).filter(
+ (char) => char.charCodeAt(0) >= 32 || char === "\n" || char === " "
+).join("");
+var count = (value, label) => {
+ if (!Number.isSafeInteger(value) || value < 0)
+ throw new TypeError(`${label} must be a nonnegative safe integer.`);
+ return value;
+};
+function summarizeBuild(input = {}) {
+ if (!input || typeof input !== "object" || Array.isArray(input))
+ throw new TypeError("Build input must be an object.");
+ const result = { label: "Build", status: "unknown" };
+ for (const key of ["label", "commit"])
+ if (input[key] !== void 0) {
+ if (typeof input[key] !== "string" || !input[key].trim() || input[key].length > 200)
+ throw new TypeError(
+ `${key} must be a nonempty string up to 200 characters.`
+ );
+ result[key] = clean(input[key].trim());
+ }
+ if (input.exitCode !== void 0) {
+ result.exitCode = input.exitCode === null ? null : count(input.exitCode, "exitCode");
+ if (input.exitCode !== null)
+ result.status = input.exitCode === 0 ? "passed" : "failed";
+ }
+ if (input.durationMs !== void 0) {
+ if (typeof input.durationMs !== "number" || !Number.isFinite(input.durationMs) || input.durationMs < 0)
+ throw new TypeError("durationMs must be a nonnegative finite number.");
+ result.durationMs = input.durationMs;
+ }
+ if (input.tests !== void 0) {
+ if (!input.tests || typeof input.tests !== "object" || Array.isArray(input.tests))
+ throw new TypeError("tests must contain measured counts.");
+ result.tests = {
+ passed: count(input.tests.passed, "tests.passed"),
+ failed: count(input.tests.failed, "tests.failed"),
+ skipped: count(input.tests.skipped ?? 0, "tests.skipped")
+ };
+ if (result.tests.failed > 0) result.status = "failed";
+ }
+ const failures = input.failures ?? [];
+ if (!Array.isArray(failures) || failures.length > 20 || !failures.every(
+ (item) => typeof item === "string" && item.trim() && item.length <= 2e3
+ ))
+ throw new TypeError(
+ "failures must contain up to 20 nonempty strings of at most 2000 characters."
+ );
+ result.failures = failures.map(clean);
+ if (failures.length) result.status = "failed";
+ result.text = [
+ `${result.label}: ${result.status.toUpperCase()}`,
+ `Build exit: ${result.exitCode === void 0 || result.exitCode === null ? "not provided" : result.exitCode}`,
+ result.tests ? `Tests: ${result.tests.passed} passed, ${result.tests.failed} failed, ${result.tests.skipped} skipped` : "Tests: not provided",
+ result.durationMs !== void 0 ? `Duration: ${result.durationMs} ms` : "Duration: not provided",
+ result.commit ? `Commit: ${result.commit}` : "Commit: not provided",
+ ...result.failures.length ? ["Failures:", ...result.failures.map((item) => "- " + item)] : []
+ ].join("\n");
+ return result;
+}
+function renderBuildSummary(input = {}, options = {}) {
+ if (!options || typeof options !== "object" || Array.isArray(options))
+ throw new TypeError("Options must be an object.");
+ const { format = "character", ...broOptions } = options;
+ if (!["character", "plain"].includes(format))
+ throw new RangeError("Summary format must be character or plain.");
+ const summary = summarizeBuild(input);
+ const rendered = format === "plain" ? summary.text : renderBro({
+ ...broOptions,
+ text: summary.text,
+ mood: broOptions.mood ?? (summary.status === "passed" ? "benji" : summary.status === "failed" ? "on-call" : "skeptic")
+ }).rendered;
+ return { status: summary.status, summary, rendered };
+}
export {
broMessage,
brosay2 as brosay,
@@ -1505,5 +1580,7 @@ export {
messageCategories,
messagePresets,
moods,
- renderBro
+ renderBro,
+ renderBuildSummary,
+ summarizeBuild
};
diff --git a/index.html b/index.html
index 4f4abda..bd06592 100644
--- a/index.html
+++ b/index.html
@@ -12,12 +12,13 @@
-
+
-
-
+
+
+
@@ -31,7 +32,7 @@
width="220"
height="220"
/>
- 0.4.0 · AVAILABLE ON NPM
+ 1.0.0 · AVAILABLE ON NPM
DeployAnyway.
Software nobody requested, built with questionable priorities, and
@@ -97,7 +98,7 @@
Serious about the work. Playful about the day.
>
- All five packages are AVAILABLE ON NPM at version 0.4.0. These demos run
+ All five packages are AVAILABLE ON NPM at version 1.0.0. These demos run
locally in your browser.
Runs entirely in your browser. No message is sent anywhere.
- npm install -g @deployanyway/bro-say@0.4.0 ·
+ npm install -g @deployanyway/bro-say@1.0.0 ·
All five packages on npm
diff --git a/siblings-1.0.0.js b/siblings-1.0.0.js
new file mode 100644
index 0000000..04d3b3f
--- /dev/null
+++ b/siblings-1.0.0.js
@@ -0,0 +1,511 @@
+import {
+ errorCatalog,
+ listErrors,
+ translateError,
+ translateErrors,
+ renderTranslation,
+} from "./vendor/error-translator/index.js";
+import {
+ listExcuses,
+ categories,
+ excuse,
+ excuseBatch,
+ excuseReport,
+} from "./vendor/excuse-js/index.js";
+import { barkLines, createDogLogger } from "./vendor/doggo-log/index.js";
+import {
+ releasePlan,
+ releaseScenarios,
+ scenarioEvidence,
+ releaseGate,
+ preflight,
+ shipIt,
+} from "./vendor/ship-it-meter/index.js";
+const el = (id) => document.getElementById(id);
+const q = (value) => "'" + String(value).replaceAll("'", "'\\''") + "'";
+const select = (id, label, values, selected) => {
+ const wrap = document.createElement("label");
+ wrap.textContent = label;
+ const input = document.createElement("select");
+ input.id = id;
+ for (const value of values) {
+ const option = document.createElement("option");
+ option.value = option.textContent = value;
+ option.selected = value === selected;
+ input.append(option);
+ }
+ wrap.append(input);
+ return wrap;
+};
+const field = (id, label, value, type = "text") => {
+ const wrap = document.createElement("label");
+ wrap.textContent = label;
+ const input = document.createElement("input");
+ input.id = id;
+ input.type = type;
+ input.value = value;
+ if (type === "number") input.min = "0";
+ wrap.append(input);
+ return wrap;
+};
+const check = (id, label, value = true) => {
+ const wrap = field(id, label, "", "checkbox");
+ wrap.firstElementChild.checked = value;
+ return wrap;
+};
+function setup(section, button, output, fields, run) {
+ const form = document.createElement("form");
+ form.className = "demo-controls";
+ form.append(...fields);
+ el(button).before(form);
+ form.addEventListener("submit", (event) => event.preventDefault());
+ const caption = document.createElement("p");
+ caption.textContent = "Try it in your terminal (sh/bash):";
+ const command = document.createElement("pre");
+ command.className = "command";
+ command.setAttribute("aria-label", `${section} command`);
+ const copy = document.createElement("button");
+ copy.type = "button";
+ copy.textContent = "Copy command";
+ const status = document.createElement("p");
+ status.setAttribute("role", "status");
+ el(output).after(caption, command, copy, status);
+ copy.addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.writeText(command.textContent);
+ status.textContent = "Command copied.";
+ } catch {
+ status.textContent = "Select and copy the command manually.";
+ }
+ });
+ const render = () => {
+ try {
+ const result = run();
+ caption.textContent =
+ result.caption || "Try it in your terminal (sh/bash):";
+ copy.textContent = result.caption ? "Copy example" : "Copy command";
+ el(output).textContent = result.output;
+ command.textContent = result.command;
+ copy.disabled = false;
+ status.textContent = "";
+ } catch (error) {
+ el(output).textContent = "Input needs attention: " + error.message;
+ command.textContent = "";
+ copy.disabled = true;
+ }
+ };
+ el(button).addEventListener("click", render);
+ form.addEventListener("input", render);
+ render();
+}
+// Catalogs are read from the actual package, so the choices match its capabilities.
+setup(
+ "error-translator",
+ "translate",
+ "translations",
+ [
+ select(
+ "error-code",
+ "Supported code / custom input",
+ [...listErrors(), "Custom message", "JSON batch", "Full catalog"],
+ "ENOENT",
+ ),
+ select(
+ "error-mode",
+ "Explanation style",
+ ["plain", "rubber-duck"],
+ "rubber-duck",
+ ),
+ select("error-format", "Output format", ["readable", "JSON"], "readable"),
+ ],
+ () => {
+ const code = el("error-code").value,
+ batch = code === "JSON batch";
+ el("errors").parentElement.hidden = ![
+ "Custom message",
+ "JSON batch",
+ ].includes(code);
+ el("errors").disabled = !["Custom message", "JSON batch"].includes(code);
+ el("errors").parentElement.firstChild.textContent = batch
+ ? "Error batch (JSON array)"
+ : "Custom error message";
+ const input = batch
+ ? JSON.parse(el("errors").value)
+ : code === "Custom message"
+ ? el("errors").value
+ : code;
+ const options = { mode: el("error-mode").value };
+ const catalog = code === "Full catalog";
+ const result = catalog
+ ? errorCatalog(options)
+ : batch
+ ? translateErrors(input, options)
+ : translateError(input, options);
+ return {
+ output:
+ el("error-format").value === "JSON"
+ ? JSON.stringify(result, null, 2)
+ : batch || catalog
+ ? result.map(renderTranslation).join("\n\n---\n\n")
+ : renderTranslation(result),
+ command:
+ (batch ? `printf '%s' ${q(el("errors").value)} | ` : "") +
+ `npx @deployanyway/error-translator@1.0.0 ${catalog ? "--catalog" : batch ? "--batch" : q(input)} --mode ${options.mode}` +
+ (el("error-format").value === "JSON" ? " --json" : ""),
+ };
+ },
+);
+for (const category of categories()) {
+ const option = document.createElement("option");
+ option.value = option.textContent = category;
+ el("excuse-category").append(option);
+}
+setup(
+ "excuse-js",
+ "excuse-report",
+ "excuse-output",
+ [
+ select(
+ "excuse-mode",
+ "Generate",
+ ["report", "single", "batch", "catalog"],
+ "report",
+ ),
+ field("excuse-seed", "Seed (blank = random)", "demo"),
+ field("excuse-count", "Batch count (1–20)", "3", "number"),
+ select("excuse-format", "Output format", ["readable", "JSON"], "readable"),
+ ],
+ () => {
+ const category = el("excuse-category").value,
+ mode = el("excuse-mode").value,
+ seed = el("excuse-seed").value,
+ options = seed ? { seed } : {};
+ el("excuse-count").disabled = mode !== "batch";
+ el("excuse-seed").disabled = mode === "catalog";
+ const result =
+ mode === "catalog"
+ ? listExcuses(category)
+ : mode === "report"
+ ? excuseReport(category, options)
+ : mode === "batch"
+ ? excuseBatch(category, {
+ ...options,
+ count: Number(el("excuse-count").value),
+ })
+ : excuse(category, options);
+ return {
+ output:
+ el("excuse-format").value === "JSON"
+ ? JSON.stringify(result, null, 2)
+ : mode === "report"
+ ? result.excuse + "\n\nNext step: " + result.nextStep
+ : Array.isArray(result)
+ ? result.map((item, i) => `${i + 1}. ${item}`).join("\n")
+ : result,
+ command:
+ `npx @deployanyway/excuse-js@1.0.0 ${category}` +
+ (seed && mode !== "catalog" ? ` --seed ${q(seed)}` : "") +
+ (mode === "catalog"
+ ? " --catalog"
+ : mode === "report"
+ ? " --report"
+ : mode === "batch"
+ ? ` --count ${el("excuse-count").value}`
+ : "") +
+ (el("excuse-format").value === "JSON" ? " --json" : ""),
+ };
+ },
+);
+el("excuse-category").addEventListener("change", () =>
+ el("excuse-report").click(),
+);
+setup(
+ "doggo-log",
+ "log-context",
+ "log-output",
+ [
+ select(
+ "dog-demo",
+ "Demonstration",
+ ["one log", "eight-log sequence", "commentary catalog"],
+ "one log",
+ ),
+ select("dog-bark-mode", "Commentary mode", ["classic", "rotate"], "rotate"),
+ field("dog-seed", "Commentary seed", "dallas"),
+ field("dog-message", "Message", "Evidence fetched. Good dog."),
+ select(
+ "dog-method",
+ "Log method",
+ ["log", "debug", "info", "success", "warn", "error"],
+ "info",
+ ),
+ select(
+ "dog-level",
+ "Minimum level",
+ ["debug", "log", "info", "success", "warn", "error"],
+ "debug",
+ ),
+ field("dog-prefix", "Prefix", "api"),
+ field(
+ "dog-context",
+ "Additional context (JSON scalar fields)",
+ '{"environment":"demo"}',
+ ),
+ select("dog-format", "Output format", ["JSON", "text"], "JSON"),
+ check("dog-bark", "Dog commentary"),
+ check("dog-emoji", "Emoji"),
+ check("dog-time", "Timestamp", false),
+ check("dog-quiet", "Quiet (suppress output)", false),
+ ],
+ () => {
+ const rawContext = JSON.parse(el("dog-context").value);
+ if (
+ !rawContext ||
+ typeof rawContext !== "object" ||
+ Array.isArray(rawContext)
+ )
+ throw new TypeError("Context must be a JSON object of scalar fields.");
+ const context = { ...rawContext, requestId: el("request-id").value };
+ const config = {
+ context,
+ prefix: el("dog-prefix").value,
+ level: el("dog-level").value,
+ json: el("dog-format").value === "JSON",
+ bark: el("dog-bark").checked,
+ barkMode: el("dog-bark-mode").value,
+ seed: el("dog-seed").value,
+ emoji: el("dog-emoji").checked,
+ timestamp: el("dog-time").checked,
+ quiet: el("dog-quiet").checked,
+ write: () => {},
+ };
+ const method = el("dog-method").value,
+ demo = el("dog-demo").value;
+ if (demo === "commentary catalog")
+ return {
+ output: barkLines(method)
+ .map((line, i) => i + 1 + ". " + line)
+ .join("\n"),
+ caption:
+ "Install @deployanyway/doggo-log@1.0.0; save as demo.mjs and run node demo.mjs:",
+ command:
+ "import { barkLines } from '@deployanyway/doggo-log';\nconsole.log(barkLines(" +
+ JSON.stringify(method) +
+ ").join('\\n'));",
+ };
+ const logger = createDogLogger(config);
+ if (demo === "eight-log sequence") {
+ const results = Array.from({ length: 8 }, () =>
+ logger[method](el("dog-message").value),
+ );
+ const codeConfig = { ...config };
+ delete codeConfig.write;
+ return {
+ output:
+ results.filter((value) => value !== undefined).join("\n") ||
+ "No logs emitted: quiet mode or minimum level filtered these messages.",
+ caption:
+ "Install @deployanyway/doggo-log@1.0.0; save as demo.mjs and run node demo.mjs:",
+ command:
+ "import { createDogLogger } from '@deployanyway/doggo-log';\nconst log = createDogLogger(" +
+ JSON.stringify(codeConfig, null, 2) +
+ ");\nfor (let i = 0; i < 8; i++) log." +
+ method +
+ "(" +
+ JSON.stringify(el("dog-message").value) +
+ ");",
+ };
+ }
+ const result = logger[method](el("dog-message").value);
+ return {
+ output:
+ result === undefined
+ ? "No log emitted: quiet mode or the minimum level filtered this message."
+ : config.json
+ ? JSON.stringify(JSON.parse(result), null, 2)
+ : result,
+ command:
+ `npx @deployanyway/doggo-log@1.0.0 ${el("dog-method").value} ${q(el("dog-message").value)} --level ${config.level} --prefix ${q(config.prefix)} --context ${q(JSON.stringify(context))}` +
+ (config.json ? " --json" : "") +
+ (config.bark ? " --bark" : "") +
+ ` --bark-mode ${config.barkMode} --seed ${q(config.seed)}` +
+ (!config.emoji ? " --no-emoji" : "") +
+ (config.timestamp ? " --timestamp" : "") +
+ (config.quiet ? " --quiet" : ""),
+ };
+ },
+);
+el("request-id").addEventListener("input", () => el("log-context").click());
+const scenarioWrap = select(
+ "ship-scenario",
+ "Example evidence scenario",
+ releaseScenarios().map((item) => item.name),
+ "ready",
+);
+const scenarioButton = document.createElement("button");
+scenarioButton.type = "button";
+scenarioButton.textContent = "Load scenario";
+const scenarioDescription = document.createElement("p");
+scenarioDescription.setAttribute("role", "status");
+el("evidence").parentElement.before(
+ scenarioWrap,
+ scenarioButton,
+ scenarioDescription,
+);
+scenarioWrap.addEventListener("change", () => {
+ scenarioDescription.textContent = releaseScenarios().find(
+ (item) => item.name === el("ship-scenario").value,
+ ).description;
+});
+scenarioButton.addEventListener("click", () => {
+ const data = scenarioEvidence(el("ship-scenario").value);
+ el("evidence").value = JSON.stringify(data, null, 2);
+ for (const [id, key] of [
+ ["ship-tests", "tests"],
+ ["ship-failing", "failingTests"],
+ ["ship-coverage", "coverage"],
+ ["ship-critical", "criticalIssues"],
+ ["ship-lint", "lintFailures"],
+ ["ship-branch", "branch"],
+ ])
+ el(id).value = data[key] ?? "";
+ el("ship-build").value =
+ data.build === undefined ? "not provided" : data.build ? "pass" : "fail";
+ el("ship-day").value = data.day ?? "monday";
+ el("ship-dirty").checked = data.uncommittedChanges ?? false;
+ scenarioDescription.textContent = releaseScenarios().find(
+ (item) => item.name === el("ship-scenario").value,
+ ).description;
+ el("gate").click();
+});
+const evidenceForm = document.createElement("form");
+evidenceForm.className = "demo-controls";
+const evidenceFields = [
+ field("ship-tests", "Total tests", "42", "number"),
+ field("ship-failing", "Failing tests", "0", "number"),
+ field("ship-coverage", "Coverage %", "92", "number"),
+ select("ship-build", "Build", ["pass", "fail", "not provided"], "pass"),
+ field("ship-critical", "Critical issues", "0", "number"),
+ field("ship-lint", "Lint failures", "0", "number"),
+ field("ship-branch", "Branch", "main"),
+ select(
+ "ship-day",
+ "Weekday",
+ [
+ "monday",
+ "tuesday",
+ "wednesday",
+ "thursday",
+ "friday",
+ "saturday",
+ "sunday",
+ ],
+ "monday",
+ ),
+ check("ship-dirty", "Uncommitted changes", false),
+];
+evidenceForm.append(...evidenceFields);
+el("evidence").parentElement.before(evidenceForm);
+evidenceForm.addEventListener("submit", (event) => event.preventDefault());
+evidenceForm.addEventListener("input", () => {
+ const data = {
+ tests: Number(el("ship-tests").value),
+ failingTests: Number(el("ship-failing").value),
+ coverage: Number(el("ship-coverage").value),
+ criticalIssues: Number(el("ship-critical").value),
+ lintFailures: Number(el("ship-lint").value),
+ branch: el("ship-branch").value,
+ day: el("ship-day").value,
+ uncommittedChanges: el("ship-dirty").checked,
+ };
+ for (const [id, key] of [
+ ["ship-tests", "tests"],
+ ["ship-failing", "failingTests"],
+ ["ship-coverage", "coverage"],
+ ["ship-critical", "criticalIssues"],
+ ["ship-lint", "lintFailures"],
+ ["ship-branch", "branch"],
+ ]) {
+ if (el(id).value === "") delete data[key];
+ }
+ if (el("ship-build").value !== "not provided")
+ data.build = el("ship-build").value === "pass";
+ el("evidence").value = JSON.stringify(data, null, 2);
+ el("gate").click();
+});
+setup(
+ "ship-it-meter",
+ "gate",
+ "gate-output",
+ [
+ select(
+ "ship-mode",
+ "Evaluation",
+ ["release plan", "release gate", "preflight checklist", "score"],
+ "release plan",
+ ),
+ field("ship-min", "Gate minimum score (0–100)", "80", "number"),
+ select("ship-format", "Output format", ["readable", "JSON"], "readable"),
+ ],
+ () => {
+ const input = JSON.parse(el("evidence").value),
+ mode = el("ship-mode").value;
+ el("ship-min").disabled = !["release gate", "release plan"].includes(mode);
+ const result =
+ mode === "release plan"
+ ? releasePlan(input, { minScore: Number(el("ship-min").value) })
+ : mode === "release gate"
+ ? releaseGate(input, { minScore: Number(el("ship-min").value) })
+ : mode === "preflight checklist"
+ ? preflight(input)
+ : shipIt(input);
+ const text = [
+ result.summary,
+ `Score: ${result.score}/100 — ${result.verdict}`,
+ result.passed === undefined
+ ? ""
+ : `Gate: ${result.passed ? "PASS" : "BLOCKED"}`,
+ ...(result.tasks
+ ? [
+ "\nRELEASE TASKS",
+ ...result.tasks.map(
+ (task) =>
+ `[${task.priority}] ${task.title}\nVerify: ${task.verify}`,
+ ),
+ ]
+ : []),
+ ...["reasons", "blockers", "actions"]
+ .filter((key) => result[key]?.length)
+ .map(
+ (key) =>
+ `\n${key.toUpperCase()}\n` +
+ result[key].map((item) => "- " + item).join("\n"),
+ ),
+ ]
+ .filter(Boolean)
+ .join("\n");
+ return {
+ output:
+ el("ship-format").value === "JSON"
+ ? JSON.stringify(result, null, 2)
+ : text,
+ command:
+ `printf '%s' ${q(el("evidence").value)} | npx @deployanyway/ship-it-meter@1.0.0 --stdin` +
+ (mode === "release plan"
+ ? ` --plan --min-score ${el("ship-min").value}`
+ : mode === "release gate"
+ ? ` --gate --min-score ${el("ship-min").value}`
+ : mode === "preflight checklist"
+ ? " --checklist"
+ : "") +
+ (el("ship-format").value === "JSON" ? " --json" : ""),
+ };
+ },
+);
+el("evidence").addEventListener("input", () => el("gate").click());
+
+// Raw evidence stays editable for missing fields and API validation examples.
+el("evidence").parentElement.firstChild.textContent =
+ "Evidence JSON (generated by controls; also directly editable)";
+
+el("errors").addEventListener("input", () => el("translate").click());
diff --git a/style-54d303f2.css b/style-54d303f2.css
index d835843..797628b 100644
--- a/style-54d303f2.css
+++ b/style-54d303f2.css
@@ -302,3 +302,28 @@ button {
width: 140px;
}
}
+
+.value-demo {
+ margin-top: 24px;
+ padding: 20px;
+ border: 1px solid #2c5568;
+ border-radius: 10px;
+ background: #111e27;
+}
+.value-demo summary {
+ cursor: pointer;
+ color: #60d5ff;
+ font-weight: 700;
+ font-size: 1.15rem;
+}
+.value-demo textarea {
+ width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ font-family: monospace;
+}
+.value-demo .terminal,
+.value-demo .command {
+ max-width: 100%;
+ overflow: auto;
+}
diff --git a/test/site.test.js b/test/site.test.js
index 73a8e26..98729a4 100644
--- a/test/site.test.js
+++ b/test/site.test.js
@@ -8,8 +8,8 @@ test("root loads released flagship controls and local assets", () => {
assert.match(html, /id="theme"/);
assert.match(html, /id="mode"/);
for (const asset of [
- "app-0.4.0.js",
- "siblings-0.4.0.js",
+ "app-1.0.0.js",
+ "siblings-1.0.0.js",
"style-54d303f2.css",
]) {
assert.ok(html.includes(asset));
diff --git a/test/value.test.js b/test/value.test.js
new file mode 100644
index 0000000..515e69d
--- /dev/null
+++ b/test/value.test.js
@@ -0,0 +1,36 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { renderBuildSummary } from "../dist/browser.js";
+import { diagnoseError } from "../vendor/error-translator/index.js";
+import { incidentUpdate } from "../vendor/excuse-js/index.js";
+import { createDogLogger } from "../vendor/doggo-log/index.js";
+import { evaluateReports } from "../vendor/ship-it-meter/index.js";
+test("v1 demo assets preserve failure, causes, missing facts, redaction and report blockers", () => {
+ assert.equal(renderBuildSummary({ exitCode: 1 }).status, "failed");
+ assert.equal(renderBuildSummary({}).status, "unknown");
+ const d = diagnoseError({
+ message: "Start failed",
+ cause: { code: "ENOENT", message: "Missing config" },
+ });
+ assert.equal(d.chain[1].code, "ENOENT");
+ assert.equal(incidentUpdate({}).complete, false);
+ assert.throws(() => incidentUpdate({}, { humor: true }), TypeError);
+ const line = JSON.parse(
+ createDogLogger({
+ json: true,
+ context: { authorization: "secret" },
+ write: () => {},
+ }).info("done"),
+ );
+ assert.equal(line.context.authorization, "[REDACTED]");
+ assert.equal(evaluateReports({ commit: "demo" }).passed, false);
+ const html = readFileSync(new URL("../index.html", import.meta.url), "utf8");
+ assert.ok(html.includes("value-1.0.0.js"));
+ const module = readFileSync(
+ new URL("../value-1.0.0.js", import.meta.url),
+ "utf8",
+ );
+ assert.match(module, /not AsyncLocalStorage/);
+ assert.match(module, /not evidence about this site/);
+});
diff --git a/value-1.0.0.js b/value-1.0.0.js
new file mode 100644
index 0000000..5be8b68
--- /dev/null
+++ b/value-1.0.0.js
@@ -0,0 +1,423 @@
+import {
+ renderBuildSummary,
+ listCharacters,
+ moods,
+ listThemes,
+} from "./dist/browser.js";
+import {
+ diagnoseError,
+ renderDiagnosis,
+} from "./vendor/error-translator/index.js";
+import { incidentUpdate } from "./vendor/excuse-js/index.js";
+import { createDogLogger } from "./vendor/doggo-log/index.js";
+import { evaluateReports } from "./vendor/ship-it-meter/index.js";
+const el = (id) => document.getElementById(id),
+ quote = (s) => "'" + String(s).replaceAll("'", "'\\''") + "'";
+const control = (id, label, value, choices) => {
+ const wrap = document.createElement("label");
+ wrap.textContent = label;
+ const input = document.createElement(
+ choices ? "select" : typeof value === "boolean" ? "input" : "textarea",
+ );
+ input.id = id;
+ if (choices)
+ for (const v of choices) {
+ const o = document.createElement("option");
+ o.value = o.textContent = v;
+ o.selected = v === value;
+ input.append(o);
+ }
+ else if (typeof value === "boolean") {
+ input.type = "checkbox";
+ input.checked = value;
+ } else {
+ input.rows = String(value).includes("\n") ? 6 : 2;
+ input.value = value;
+ }
+ wrap.append(input);
+ return wrap;
+};
+const val = (id) => el(id).value,
+ json = (id) => JSON.parse(val(id));
+function panel(anchor, id, title, description, fields, run) {
+ const details = document.createElement("details");
+ details.open = true;
+ details.className = "value-demo";
+ const summary = document.createElement("summary");
+ summary.textContent = title;
+ const intro = document.createElement("p");
+ intro.textContent = description;
+ const form = document.createElement("form");
+ form.className = "demo-controls";
+ form.append(...fields);
+ const output = document.createElement("pre");
+ output.id = id + "-output";
+ output.className = "terminal";
+ output.setAttribute("aria-live", "polite");
+ const caption = document.createElement("p"),
+ code = document.createElement("pre");
+ code.className = "command";
+ code.id = id + "-command";
+ const button = document.createElement("button");
+ button.type = "button";
+ button.textContent = "Run example";
+ const copy = document.createElement("button");
+ copy.type = "button";
+ copy.textContent = "Copy example";
+ const status = document.createElement("p");
+ status.setAttribute("role", "status");
+ const render = () => {
+ try {
+ const r = run();
+ output.textContent = r.output;
+ code.textContent = r.command;
+ caption.textContent =
+ r.caption ?? "Try the same input in your terminal (sh/bash):";
+ copy.disabled = false;
+ status.textContent = "";
+ } catch (e) {
+ output.textContent = "Input needs attention: " + e.message;
+ code.textContent = "";
+ copy.disabled = true;
+ }
+ };
+ form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ render();
+ });
+ form.addEventListener("input", render);
+ button.addEventListener("click", render);
+ copy.addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.writeText(code.textContent);
+ status.textContent = "Example copied.";
+ } catch {
+ status.textContent = "Select and copy the example manually.";
+ }
+ });
+ details.append(
+ summary,
+ intro,
+ form,
+ button,
+ output,
+ caption,
+ code,
+ copy,
+ status,
+ );
+ el(anchor).closest("section").append(details);
+ render();
+}
+const pretty = (v) => JSON.stringify(v, null, 2),
+ pipe = (pkg, flags, data) =>
+ `printf '%s' ${quote(data)} | npx @deployanyway/${pkg}@1.0.0 ${flags}`;
+panel(
+ "output",
+ "build",
+ "Build Buddy: actual facts, original characters",
+ "Summarize your measured build and test results. Missing build status stays unknown. This renders facts; your script must preserve the underlying command exit code.",
+ [
+ control(
+ "build-facts",
+ "Measured facts (JSON)",
+ pretty({
+ label: "Release build",
+ exitCode: 0,
+ tests: { passed: 42, failed: 0, skipped: 2 },
+ durationMs: 1520,
+ commit: "demo-commit",
+ }),
+ ),
+ control("build-format", "Summary output", "character", [
+ "character",
+ "plain",
+ "JSON",
+ ]),
+ control("build-character", "Character", "husky", listCharacters()),
+ control("build-mood", "Mood", "benji", moods()),
+ control("build-theme", "Theme", "classic", listThemes()),
+ control("build-mode", "Say or think", "say", ["say", "think"]),
+ control("build-width", "Wrap width", "52"),
+ ],
+ () => {
+ const format = val("build-format"),
+ r = renderBuildSummary(json("build-facts"), {
+ format: format === "plain" ? "plain" : "character",
+ character: val("build-character"),
+ mood: val("build-mood"),
+ theme: val("build-theme"),
+ mode: val("build-mode"),
+ width: Number(val("build-width")),
+ });
+ return {
+ output: format === "JSON" ? pretty(r) : r.rendered,
+ command: pipe(
+ "bro-say",
+ "--summary" +
+ (format === "plain"
+ ? " --plain"
+ : ` --character ${val("build-character")} --mood ${val("build-mood")} --theme ${val("build-theme")} --width ${val("build-width")}`) +
+ (val("build-mode") === "think" && format !== "plain"
+ ? " --think"
+ : "") +
+ (format === "JSON" ? " --json" : ""),
+ val("build-facts"),
+ ),
+ };
+ },
+);
+panel(
+ "translate",
+ "diagnosis",
+ "Follow the cause, not just the error code",
+ "Paste an Error-shaped JSON object with nested cause fields. Bounded traversal handles cycles in the API; stacks are opt-in. The Node API also preserves your original Error by reference.",
+ [
+ control(
+ "diagnosis-facts",
+ "Error and cause (JSON)",
+ pretty({
+ name: "Error",
+ message: "Configuration could not load",
+ cause: {
+ name: "Error",
+ code: "ENOENT",
+ message: "Missing config.json",
+ },
+ }),
+ ),
+ control("diagnosis-mode", "Explanation style", "plain", [
+ "plain",
+ "rubber-duck",
+ ]),
+ control("diagnosis-depth", "Maximum depth (1–32)", "8"),
+ control("diagnosis-stack", "Include supplied stack", false),
+ control("diagnosis-format", "Output", "readable", ["readable", "JSON"]),
+ ],
+ () => {
+ const options = {
+ mode: val("diagnosis-mode"),
+ maxDepth: Number(val("diagnosis-depth")),
+ includeStack: el("diagnosis-stack").checked,
+ },
+ r = diagnoseError(json("diagnosis-facts"), options);
+ return {
+ output:
+ val("diagnosis-format") === "JSON" ? pretty(r) : renderDiagnosis(r),
+ command: pipe(
+ "error-translator",
+ `--diagnose --mode ${options.mode} --max-depth ${options.maxDepth}` +
+ (options.includeStack ? " --include-stack" : "") +
+ (val("diagnosis-format") === "JSON" ? " --json" : ""),
+ val("diagnosis-facts"),
+ ),
+ };
+ },
+);
+panel(
+ "excuse-report",
+ "incident",
+ "Own It: accountable incident updates",
+ "Draft from facts you provide. Missing facts remain visible; complete means fields supplied, not incident resolved. Public updates keep jokes off. Nothing is posted or sent.",
+ [
+ control(
+ "incident-facts",
+ "Known facts (JSON)",
+ pretty({
+ status: "identified",
+ service: "API",
+ impact: "Some requests return 503",
+ action: "Rolling back the latest release",
+ owner: "On-call developer",
+ nextUpdate: new Date(Date.now() + 900000).toISOString(),
+ }),
+ ),
+ control("incident-audience", "Audience", "public", ["public", "internal"]),
+ control(
+ "incident-humor",
+ "Internal comic relief (requires internal audience)",
+ false,
+ ),
+ control("incident-seed", "Humor seed", "dallas"),
+ control("incident-format", "Output", "readable", ["readable", "JSON"]),
+ control("incident-complete", "Require complete facts in CLI", true),
+ ],
+ () => {
+ const options = {
+ audience: val("incident-audience"),
+ humor: el("incident-humor").checked,
+ seed: val("incident-seed"),
+ },
+ r = incidentUpdate(json("incident-facts"), options);
+ return {
+ output: val("incident-format") === "JSON" ? pretty(r) : r.message,
+ command: pipe(
+ "excuse-js",
+ `--incident --audience ${options.audience}` +
+ (options.humor ? " --humor --seed " + quote(options.seed) : "") +
+ (val("incident-format") === "JSON" ? " --json" : "") +
+ (el("incident-complete").checked ? " --require-complete" : ""),
+ val("incident-facts"),
+ ),
+ };
+ },
+);
+panel(
+ "log-context",
+ "redaction",
+ "Good dog. Guard the context.",
+ "Try redaction before output reaches a writer. Credential context keys are protected by default; literal secrets require configuration. The Node-only request logger carries scope through async work. Browser output below demonstrates the shared formatting/redaction core, not AsyncLocalStorage.",
+ [
+ control(
+ "redaction-context",
+ "Request context (scalar JSON)",
+ pretty({
+ requestId: "dallas-1",
+ authorization: "demo-secret",
+ service: "API",
+ }),
+ ),
+ control(
+ "redaction-message",
+ "Message",
+ "Request finished with demo-secret",
+ ),
+ control(
+ "redaction-values",
+ "Exact secret values (JSON array)",
+ '["demo-secret"]',
+ ),
+ control(
+ "redaction-keys",
+ "Custom protected keys (blank uses defaults)",
+ "",
+ ),
+ control("redaction-replacement", "Replacement", "[REDACTED]"),
+ control("redaction-format", "Output", "JSON", ["JSON", "text"]),
+ control("redaction-method", "Method", "info", [
+ "log",
+ "debug",
+ "info",
+ "success",
+ "warn",
+ "error",
+ ]),
+ ],
+ () => {
+ const redact = {
+ values: json("redaction-values"),
+ replacement: val("redaction-replacement"),
+ };
+ if (val("redaction-keys").trim())
+ redact.keys = val("redaction-keys")
+ .split(",")
+ .map((s) => s.trim());
+ const config = {
+ json: val("redaction-format") === "JSON",
+ redact,
+ bark: true,
+ barkMode: "rotate",
+ seed: "dallas",
+ },
+ context = json("redaction-context"),
+ method = val("redaction-method"),
+ message = val("redaction-message");
+ const line = createDogLogger({ ...config, context, write: () => {} })[
+ method
+ ](message);
+ return {
+ output: config.json ? pretty(JSON.parse(line)) : line,
+ caption:
+ "Node 22.13+/24: install @deployanyway/doggo-log@1.0.0, save as demo.mjs, run node demo.mjs. This example runs the actual asynchronous scope API:",
+ command: `import { createRequestLogger } from '@deployanyway/doggo-log/context';\nconst log = createRequestLogger(${pretty(config)});\ntry {\n await log.run(${pretty(context)}, async () => {\n await new Promise(resolve => setTimeout(resolve, 10));\n log.${method}(${JSON.stringify(message)});\n });\n} finally { log.dispose(); }`,
+ };
+ },
+);
+const stamp = new Date().toISOString(),
+ commit = "demo-commit";
+const cov = {
+ total: Object.fromEntries(
+ ["lines", "statements", "functions", "branches"].map((k) => [
+ k,
+ { total: 10, covered: 10, pct: 100 },
+ ]),
+ ),
+};
+const tap =
+ "TAP version 13\n1..1\n# tests 1\n# pass 1\n# fail 0\n# cancelled 0\n# skipped 0\n# todo 0\n";
+panel(
+ "gate",
+ "receipts",
+ "Bring receipts: a report-backed release policy",
+ "Demonstration reports are editable sample data, not evidence about this site. In CI, collect real completed reports for the exact commit. Receipt metadata is a pipeline claim, not a signature; do not trust unverified submitted artifacts.",
+ [
+ control("receipt-commit", "Expected commit", commit),
+ control("receipt-source-commit", "Report commit", commit),
+ control("receipt-time", "Report capture time (UTC ISO)", stamp),
+ control("receipt-format", "Test report format", "node-tap", [
+ "node-tap",
+ "jest",
+ ]),
+ control(
+ "receipt-tests",
+ "Completed test report (Node TAP or Jest JSON)",
+ tap,
+ ),
+ control(
+ "receipt-coverage",
+ "Istanbul / c8 coverage-summary.json",
+ pretty(cov),
+ ),
+ control("receipt-build", "Measured build exit code", "0"),
+ control(
+ "receipt-policy",
+ "Policy (JSON)",
+ pretty({ minTests: 1, minCoverage: 80, minScore: 80, maxAgeMs: 3600000 }),
+ ),
+ control("receipt-output", "Output", "readable", ["readable", "JSON"]),
+ ],
+ () => {
+ const meta = {
+ commit: val("receipt-source-commit"),
+ capturedAt: val("receipt-time"),
+ },
+ bundle = {
+ commit: val("receipt-commit"),
+ tests: {
+ ...meta,
+ format: val("receipt-format"),
+ data: val("receipt-tests"),
+ },
+ coverage: {
+ ...meta,
+ format: "istanbul-summary",
+ data: json("receipt-coverage"),
+ },
+ build: { ...meta, exitCode: Number(val("receipt-build")) },
+ },
+ policy = json("receipt-policy"),
+ r = evaluateReports(bundle, policy);
+ return {
+ output:
+ val("receipt-output") === "JSON"
+ ? pretty(r)
+ : [
+ r.summary,
+ `Gate: ${r.passed ? "PASS" : "BLOCKED"}`,
+ `Score: ${r.score}/100`,
+ ...r.receipts.map(
+ (x) =>
+ `${x.kind}: ${x.accepted ? "accepted" : x.issues.join("; ")}`,
+ ),
+ ...r.blockers,
+ ].join("\n"),
+ command: pipe(
+ "ship-it-meter",
+ "--reports --policy " +
+ quote(JSON.stringify(policy)) +
+ (val("receipt-output") === "JSON" ? " --json" : ""),
+ JSON.stringify(bundle),
+ ),
+ };
+ },
+);
diff --git a/vendor/doggo-log/index.js b/vendor/doggo-log/index.js
index c19772b..ea76407 100644
--- a/vendor/doggo-log/index.js
+++ b/vendor/doggo-log/index.js
@@ -1,6 +1,7 @@
import { format } from "node:util";
import { levels } from "./levels.js";
import { barkLines, commentaryIndex } from "./commentary.js";
+import { copyContext, createRedactor } from "./redaction.js";
export { barkLines };
/**
@@ -22,6 +23,7 @@ export function createDogLogger(options = {}) {
prefix: "",
level: "info",
context: {},
+ contextProvider: () => ({}),
write: (line, level) =>
level === "warn" || level === "error"
? console.error(line)
@@ -51,7 +53,16 @@ export function createDogLogger(options = {}) {
throw new TypeError(
"Context values must be strings, finite numbers, booleans or null.",
);
- config.context = { ...config.context };
+ config.context = copyContext(config.context);
+ if (typeof config.contextProvider !== "function")
+ throw new TypeError("contextProvider must be a function.");
+ const redactor = createRedactor(config.redact);
+ if (config.redact !== false)
+ config.redact = {
+ ...config.redact,
+ ...(config.redact?.keys ? { keys: [...config.redact.keys] } : {}),
+ ...(config.redact?.values ? { values: [...config.redact.values] } : {}),
+ };
if (typeof config.level !== "string" || !Object.hasOwn(levels, config.level))
throw new RangeError(
`level must be one of: ${Object.keys(levels).join(", ")}.`,
@@ -73,7 +84,11 @@ export function createDogLogger(options = {}) {
(...args) => {
if (config.quiet || style.rank < levels[config.level].rank)
return undefined;
- const message = format(...args);
+ const message = redactor.text(format(...args));
+ const context = redactor.context({
+ ...config.context,
+ ...copyContext(config.contextProvider()),
+ });
const pool = config.bark ? barkLines(level) : undefined;
const index =
config.barkMode === "rotate"
@@ -98,20 +113,19 @@ export function createDogLogger(options = {}) {
line = JSON.stringify({
level,
message,
- ...(Object.keys(config.context).length
- ? { context: { ...config.context } }
- : {}),
+ ...(Object.keys(context).length ? { context } : {}),
...(commentary ? { commentary } : {}),
- ...(config.prefix ? { prefix: config.prefix } : {}),
+ ...(config.prefix ? { prefix: redactor.text(config.prefix) } : {}),
...(timestamp ? { timestamp } : {}),
});
} else {
line = [
timestamp,
- config.prefix,
+ redactor.text(config.prefix),
config.emoji ? style.emoji : undefined,
level.toUpperCase().padEnd(7),
message,
+ Object.keys(context).length ? JSON.stringify(context) : undefined,
commentary,
]
.filter((part) => part !== undefined && part !== "")
diff --git a/vendor/doggo-log/redaction.js b/vendor/doggo-log/redaction.js
new file mode 100644
index 0000000..104d801
--- /dev/null
+++ b/vendor/doggo-log/redaction.js
@@ -0,0 +1,73 @@
+const defaultKeys = [
+ "password",
+ "passwd",
+ "token",
+ "accessToken",
+ "refreshToken",
+ "authorization",
+ "cookie",
+ "secret",
+ "apiKey",
+];
+const normalize = (key) => key.toLowerCase().replaceAll(/[-_]/g, "");
+export function copyContext(context) {
+ if (!context || typeof context !== "object" || Array.isArray(context))
+ throw new TypeError("context must be an object of scalar fields.");
+ for (const value of Object.values(context))
+ if (!(
+ value === null ||
+ typeof value === "string" ||
+ typeof value === "boolean" ||
+ (typeof value === "number" && Number.isFinite(value))
+ ))
+ throw new TypeError(
+ "Context values must be strings, finite numbers, booleans or null.",
+ );
+ return { ...context };
+}
+export function createRedactor(options) {
+ if (options === false)
+ return { text: (value) => value, context: copyContext };
+ if (options === undefined) options = {};
+ if (!options || typeof options !== "object" || Array.isArray(options))
+ throw new TypeError("redact must be false or an options object.");
+ const keys = options.keys ?? defaultKeys,
+ values = options.values ?? [],
+ replacement = options.replacement ?? "[REDACTED]";
+ for (const [name, list] of [
+ ["keys", keys],
+ ["values", values],
+ ])
+ if (
+ !Array.isArray(list) ||
+ list.length > 100 ||
+ !list.every((value) => typeof value === "string" && value.length > 0)
+ )
+ throw new TypeError(
+ `Redaction ${name} must contain at most 100 nonempty strings.`,
+ );
+ if (typeof replacement !== "string")
+ throw new TypeError("Redaction replacement must be a string.");
+ const protectedKeys = new Set(keys.map(normalize));
+ // Longest first prevents a short secret from exposing a suffix of a longer one.
+ const literals = [...new Set(values)].sort((a, b) => b.length - a.length);
+ const text = (value) =>
+ literals.reduce(
+ (output, secret) => output.split(secret).join(replacement),
+ value,
+ );
+ return {
+ text,
+ context: (context) =>
+ Object.fromEntries(
+ Object.entries(copyContext(context)).map(([key, value]) => [
+ key,
+ protectedKeys.has(normalize(key))
+ ? replacement
+ : typeof value === "string"
+ ? text(value)
+ : value,
+ ]),
+ ),
+ };
+}
diff --git a/vendor/error-translator/diagnostics.js b/vendor/error-translator/diagnostics.js
new file mode 100644
index 0000000..2e38dac
--- /dev/null
+++ b/vendor/error-translator/diagnostics.js
@@ -0,0 +1,79 @@
+import { translateError, renderTranslation } from "./index.js";
+
+/** Snapshot a bounded cause chain; preserve the original without serializing it. */
+export function diagnoseError(error, options = {}) {
+ if (!options || typeof options !== "object" || Array.isArray(options))
+ throw new TypeError("Options must be an object.");
+ const maxDepth = options.maxDepth ?? 8,
+ includeStack = options.includeStack ?? false;
+ if (!Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > 32)
+ throw new RangeError("maxDepth must be an integer from 1 to 32.");
+ if (typeof includeStack !== "boolean")
+ throw new TypeError("includeStack must be a boolean.");
+ // Validate the root using the established translation contract.
+ translateError(error, options);
+ const seen = new Set(),
+ chain = [];
+ let current = error,
+ stopped = "complete";
+ while (current !== undefined) {
+ if (chain.length === maxDepth) {
+ stopped = "depth-limit";
+ break;
+ }
+ if (current !== null && typeof current === "object") {
+ if (seen.has(current)) {
+ stopped = "cycle";
+ break;
+ }
+ seen.add(current);
+ }
+ const object =
+ current !== null && typeof current === "object" && !Array.isArray(current)
+ ? current
+ : undefined;
+ const raw = typeof current === "string" ? current : object?.message;
+ const message = typeof raw === "string" ? raw : String(current);
+ const name = typeof object?.name === "string" ? object.name : "Error";
+ const code = typeof object?.code === "string" ? object.code : undefined;
+ const translation = translateError(
+ { name, message, ...(code ? { code } : {}) },
+ options,
+ );
+ chain.push({
+ depth: chain.length,
+ name,
+ message,
+ ...(code ? { code } : {}),
+ translation,
+ ...(includeStack && typeof object?.stack === "string"
+ ? { stack: object.stack }
+ : {}),
+ });
+ current = object?.cause;
+ }
+ const result = { chain, stopped, truncated: stopped !== "complete" };
+ Object.defineProperty(result, "original", {
+ value: error,
+ enumerable: false,
+ });
+ return result;
+}
+export function renderDiagnosis(result) {
+ if (
+ !result ||
+ !Array.isArray(result.chain) ||
+ !result.chain.length ||
+ !["complete", "cycle", "depth-limit"].includes(result.stopped)
+ )
+ throw new TypeError("Provide a diagnosis result.");
+ return (
+ result.chain
+ .map(
+ (item, index) =>
+ `${index === 0 ? "Failure" : "Caused by"}: ${item.name}${item.code ? " [" + item.code + "]" : ""}: ${item.message}\n${renderTranslation(item.translation)}${item.stack ? "\n\nOriginal stack:\n" + item.stack : ""}`,
+ )
+ .join("\n\n---\n\n") +
+ (result.truncated ? `\n\nCause traversal stopped: ${result.stopped}.` : "")
+ );
+}
diff --git a/vendor/error-translator/index.js b/vendor/error-translator/index.js
index a20ed7e..b22be53 100644
--- a/vendor/error-translator/index.js
+++ b/vendor/error-translator/index.js
@@ -1,4 +1,5 @@
import { definitions } from "./definitions.js";
+export { diagnoseError, renderDiagnosis } from "./diagnostics.js";
export const listErrors = () => Object.keys(definitions);
/** Translate an ordered, bounded batch without mutating input. */
diff --git a/vendor/excuse-js/incident.js b/vendor/excuse-js/incident.js
new file mode 100644
index 0000000..95fe42d
--- /dev/null
+++ b/vendor/excuse-js/incident.js
@@ -0,0 +1,83 @@
+import { excuse } from "./index.js";
+const iso = (value, label) => {
+ if (
+ typeof value !== "string" ||
+ !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,3})?Z$/.test(value) ||
+ !Number.isFinite(Date.parse(value)) ||
+ new Date(value).toISOString().slice(0, 19) !== value.slice(0, 19)
+ )
+ throw new TypeError(`${label} must be a UTC ISO timestamp.`);
+ return value;
+};
+const clean = (value) =>
+ Array.from(value)
+ .filter(
+ (char) => char.charCodeAt(0) >= 32 || char === "\n" || char === "\t",
+ )
+ .join("");
+/** Draft factual incident communication; public output never adds a joke. */
+export function incidentUpdate(facts = {}, options = {}) {
+ if (!facts || typeof facts !== "object" || Array.isArray(facts))
+ throw new TypeError("Incident facts must be an object.");
+ if (!options || typeof options !== "object" || Array.isArray(options))
+ throw new TypeError("Options must be an object.");
+ const status = facts.status ?? "investigating",
+ audience = options.audience ?? "public",
+ humor = options.humor ?? false;
+ if (
+ !["investigating", "identified", "monitoring", "resolved"].includes(status)
+ )
+ throw new RangeError("Unsupported incident status.");
+ if (!["public", "internal"].includes(audience) || typeof humor !== "boolean")
+ throw new TypeError("Choose public/internal audience and boolean humor.");
+ if (humor && audience !== "internal")
+ throw new TypeError("Comic relief requires an internal audience.");
+ const result = { status, audience, missing: [], overdue: false };
+ for (const key of ["service", "impact", "action", "owner"]) {
+ if (
+ facts[key] !== undefined &&
+ facts[key] !== null &&
+ (typeof facts[key] !== "string" ||
+ !facts[key].trim() ||
+ facts[key].length > 2000)
+ )
+ throw new TypeError(
+ `${key} must be a nonempty string up to 2000 characters.`,
+ );
+ result[key] =
+ facts[key] === undefined || facts[key] === null
+ ? null
+ : clean(facts[key].trim());
+ if (result[key] === null) result.missing.push(key);
+ }
+ const now = iso(options.now ?? new Date().toISOString(), "now");
+ result.nextUpdate =
+ facts.nextUpdate === undefined || facts.nextUpdate === null
+ ? null
+ : iso(facts.nextUpdate, "nextUpdate");
+ if (result.nextUpdate === null && status !== "resolved")
+ result.missing.push("nextUpdate");
+ result.overdue =
+ status !== "resolved" &&
+ result.nextUpdate !== null &&
+ Date.parse(result.nextUpdate) < Date.parse(now);
+ const lines = [
+ `Status: ${status}`,
+ `Service: ${result.service ?? "Not provided"}`,
+ `Impact: ${result.impact ?? "Not provided — verify before claiming an impact"}`,
+ `Current action: ${result.action ?? "Not provided"}`,
+ `Owner: ${result.owner ?? "Not assigned"}`,
+ `Next update: ${result.nextUpdate ?? (status === "resolved" ? "Not scheduled (status marked resolved)" : "Not scheduled")}${result.overdue ? " — OVERDUE" : ""}`,
+ ];
+ if (result.missing.length)
+ lines.push("Missing facts: " + result.missing.join(", "));
+ result.complete = result.missing.length === 0;
+ if (humor) {
+ result.comicRelief = excuse(options.category ?? "production", {
+ seed: options.seed,
+ });
+ lines.push("\nComic relief (internal only): " + result.comicRelief);
+ }
+ result.message = lines.join("\n");
+ return result;
+}
diff --git a/vendor/excuse-js/index.js b/vendor/excuse-js/index.js
index 4b211f6..76a44eb 100644
--- a/vendor/excuse-js/index.js
+++ b/vendor/excuse-js/index.js
@@ -102,3 +102,4 @@ export function listExcuses(category = "deployment") {
throw new RangeError("Unknown category: " + category);
return [...phrases[key]];
}
+export { incidentUpdate } from "./incident.js";
diff --git a/vendor/ship-it-meter/index.js b/vendor/ship-it-meter/index.js
index 72cbd77..5f5ff81 100644
--- a/vendor/ship-it-meter/index.js
+++ b/vendor/ship-it-meter/index.js
@@ -297,3 +297,8 @@ export function releasePlan(input = {}, options = {}) {
tasks.sort((a, b) => rank[a.priority] - rank[b.priority]);
return { ...gate, summary: summaries[gate.verdict], tasks };
}
+export {
+ parseTestReport,
+ parseCoverageReport,
+ evaluateReports,
+} from "./reports.js";
diff --git a/vendor/ship-it-meter/reports.js b/vendor/ship-it-meter/reports.js
new file mode 100644
index 0000000..95688ae
--- /dev/null
+++ b/vendor/ship-it-meter/reports.js
@@ -0,0 +1,262 @@
+import { releasePlan } from "./index.js";
+const object = (value, label) => {
+ if (!value || typeof value !== "object" || Array.isArray(value))
+ throw new TypeError(`${label} must be an object.`);
+ return value;
+};
+const count = (value, label) => {
+ if (!Number.isSafeInteger(value) || value < 0)
+ throw new TypeError(`${label} must be a nonnegative safe integer.`);
+ return value;
+};
+const json = (value) => (typeof value === "string" ? JSON.parse(value) : value);
+const date = (value, label) => {
+ if (
+ typeof value !== "string" ||
+ !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,3})?Z$/.test(value) ||
+ !Number.isFinite(Date.parse(value)) ||
+ new Date(value).toISOString().slice(0, 19) !== value.slice(0, 19)
+ )
+ throw new TypeError(`${label} must be a UTC ISO timestamp.`);
+ return Date.parse(value);
+};
+
+/** Parse completed Node TAP or Jest JSON test results; skipped/todo tests are not passing evidence. */
+export function parseTestReport(data, format = "node-tap") {
+ if (format === "node-tap") {
+ if (
+ typeof data !== "string" ||
+ !data.startsWith("TAP version 13") ||
+ /^Bail out!/m.test(data)
+ )
+ throw new TypeError("Expected a completed Node TAP v13 report.");
+ const values = {};
+ for (const key of [
+ "tests",
+ "pass",
+ "fail",
+ "cancelled",
+ "skipped",
+ "todo",
+ ]) {
+ const matches = [
+ ...data.matchAll(new RegExp(`^# ${key} (\\d+)\\r?$`, "gm")),
+ ];
+ if (matches.length !== 1)
+ throw new TypeError(`TAP requires one ${key} summary.`);
+ values[key] = count(Number(matches[0][1]), key);
+ }
+ if (
+ values.pass +
+ values.fail +
+ values.cancelled +
+ values.skipped +
+ values.todo !==
+ values.tests
+ )
+ throw new TypeError("TAP counts do not reconcile.");
+ return {
+ tests: values.tests - values.skipped - values.todo,
+ passedTests: values.pass,
+ failingTests: values.fail + values.cancelled,
+ skippedTests: values.skipped,
+ todoTests: values.todo,
+ successful: values.fail + values.cancelled === 0,
+ };
+ }
+ if (format === "jest") {
+ const report = object(json(data), "Jest report");
+ const total = count(report.numTotalTests, "numTotalTests"),
+ passed = count(report.numPassedTests, "numPassedTests"),
+ failed = count(report.numFailedTests, "numFailedTests"),
+ pending = count(report.numPendingTests, "numPendingTests"),
+ todo = count(report.numTodoTests ?? 0, "numTodoTests");
+ if (
+ total !== passed + failed + pending + todo ||
+ typeof report.success !== "boolean"
+ )
+ throw new TypeError("Jest counts/success do not reconcile.");
+ if (
+ report.wasInterrupted ||
+ report.testResults?.some((result) =>
+ result.assertionResults?.some(
+ (assertion) => assertion.wouldRun === true,
+ ),
+ )
+ )
+ throw new TypeError(
+ "Interrupted or collected-only Jest output is not executed evidence.",
+ );
+ const failedSuites = count(
+ report.numFailedTestSuites ?? 0,
+ "numFailedTestSuites",
+ );
+ return {
+ tests: total - pending - todo,
+ passedTests: passed,
+ failingTests: failed,
+ skippedTests: pending,
+ todoTests: todo,
+ successful: report.success && failed === 0 && failedSuites === 0,
+ };
+ }
+ throw new RangeError("Supported test formats: node-tap, jest.");
+}
+/** Read Istanbul/c8 coverage-summary.json. Empty instrumented code is not evidence. */
+export function parseCoverageReport(data) {
+ const total = object(
+ object(json(data), "Coverage report").total,
+ "Coverage total",
+ ),
+ result = {};
+ for (const key of ["lines", "statements", "functions", "branches"]) {
+ const metric = object(total[key], `Coverage ${key}`),
+ all = count(metric.total, `${key}.total`),
+ covered = count(metric.covered, `${key}.covered`);
+ if (
+ (all === 0 && ["lines", "statements"].includes(key)) ||
+ covered > all ||
+ typeof metric.pct !== "number" ||
+ !Number.isFinite(metric.pct) ||
+ metric.pct < 0 ||
+ metric.pct > 100
+ )
+ throw new TypeError(
+ `Coverage ${key} must contain measured code and a percentage.`,
+ );
+ const computed = all === 0 ? 100 : (covered / all) * 100;
+ if (Math.abs(computed - metric.pct) > 0.02)
+ throw new TypeError(`Coverage ${key} percentage disagrees with counts.`);
+ result[key] = metric.pct;
+ }
+ return result;
+}
+/** Gate real report envelopes. Caller-supplied commit/timestamps are provenance claims, not signatures. */
+export function evaluateReports(bundle, options = {}) {
+ object(bundle, "Report bundle");
+ object(options, "Policy");
+ for (const key of Object.keys(options))
+ if (
+ !["minTests", "minCoverage", "minScore", "maxAgeMs", "now"].includes(key)
+ )
+ throw new TypeError(`Unknown policy option: ${key}.`);
+ if (typeof bundle.commit !== "string" || !bundle.commit.trim())
+ throw new TypeError("Report bundle requires a nonempty commit.");
+ const policy = {
+ minTests: 1,
+ minCoverage: 80,
+ minScore: 80,
+ maxAgeMs: 3600000,
+ ...options,
+ };
+ for (const [key, value] of Object.entries({
+ minTests: 1,
+ minCoverage: 80,
+ minScore: 80,
+ maxAgeMs: 3600000,
+ }))
+ if (policy[key] === undefined) policy[key] = value;
+ count(policy.minTests, "minTests");
+ if (policy.minTests < 1) throw new RangeError("minTests must be positive.");
+ for (const key of ["minCoverage", "minScore"])
+ if (
+ typeof policy[key] !== "number" ||
+ !Number.isFinite(policy[key]) ||
+ policy[key] < 0 ||
+ policy[key] > 100
+ )
+ throw new RangeError(`${key} must be from 0 to 100.`);
+ if (!Number.isSafeInteger(policy.maxAgeMs) || policy.maxAgeMs < 1)
+ throw new RangeError("maxAgeMs must be a positive safe integer.");
+ const now = options.now ?? new Date().toISOString();
+ const nowMs = date(now, "now");
+ const evidence = {},
+ receipts = [],
+ blockers = [];
+ for (const kind of ["tests", "coverage", "build"]) {
+ const issues = [],
+ report = bundle[kind];
+ if (!report) {
+ issues.push(`${kind}: report is missing.`);
+ } else
+ try {
+ object(report, kind);
+ const captured = date(report.capturedAt, `${kind}.capturedAt`);
+ if (report.commit !== bundle.commit)
+ issues.push(`${kind}: commit does not match ${bundle.commit}.`);
+ if (captured > nowMs)
+ issues.push(`${kind}: timestamp is in the future.`);
+ if (nowMs - captured > policy.maxAgeMs)
+ issues.push(`${kind}: report is stale.`);
+ if (kind === "tests") {
+ if (!["node-tap", "jest"].includes(report.format))
+ throw new RangeError("Test format must be node-tap or jest.");
+ const parsed = parseTestReport(report.data, report.format);
+ evidence.tests = parsed.tests;
+ evidence.failingTests = parsed.failingTests;
+ if (!parsed.successful)
+ issues.push("tests: test execution was unsuccessful.");
+ if (parsed.tests < policy.minTests)
+ issues.push(
+ `tests: ${parsed.tests} executed tests is below ${policy.minTests}.`,
+ );
+ } else if (kind === "coverage") {
+ if (report.format !== "istanbul-summary")
+ throw new RangeError("Coverage format must be istanbul-summary.");
+ const coverage = parseCoverageReport(report.data);
+ evidence.coverage = coverage.lines;
+ for (const [metric, value] of Object.entries(coverage))
+ if (value < policy.minCoverage)
+ issues.push(
+ `coverage: ${metric} ${value}% is below ${policy.minCoverage}%.`,
+ );
+ } else {
+ count(report.exitCode, "build.exitCode");
+ evidence.build = report.exitCode === 0;
+ if (report.exitCode !== 0)
+ issues.push(`build: command exited ${report.exitCode}.`);
+ }
+ } catch (error) {
+ issues.push(`${kind}: ${error.message}`);
+ }
+ receipts.push({
+ kind,
+ format: kind === "build" ? "exit-code" : (report?.format ?? "missing"),
+ commit: typeof report?.commit === "string" ? report.commit : null,
+ capturedAt:
+ typeof report?.capturedAt === "string" ? report.capturedAt : null,
+ accepted: issues.length === 0,
+ issues,
+ });
+ blockers.push(...issues);
+ }
+ const plan = releasePlan(evidence, { minScore: policy.minScore });
+ const combined = [...new Set([...blockers, ...plan.blockers])];
+ const receiptTasks = receipts
+ .filter((item) => !item.accepted)
+ .map((item) => ({
+ id: "receipt-" + item.kind,
+ priority: "blocker",
+ title: "Repair " + item.kind + " evidence",
+ verify: `Generate a completed ${item.kind} report for ${bundle.commit} with a current UTC timestamp; resolve: ${item.issues.join(" ")}`,
+ }));
+ return {
+ ...plan,
+ summary: combined.length
+ ? "The receipts say stop. Confidence must wait."
+ : plan.summary,
+ tasks: [...receiptTasks, ...plan.tasks],
+ passed: combined.length === 0,
+ blockers: combined,
+ evidence,
+ receipts,
+ commit: bundle.commit,
+ policy: {
+ minTests: policy.minTests,
+ minCoverage: policy.minCoverage,
+ minScore: policy.minScore,
+ maxAgeMs: policy.maxAgeMs,
+ },
+ evaluatedAt: now,
+ };
+}