@jstn-sdk/ma 0.1.12 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex/agents/Architect.toml +1 -2
- package/.codex/agents/Auditor.toml +1 -2
- package/.codex/agents/Builder.toml +1 -2
- package/.codex/agents/Flow.toml +1 -2
- package/.codex/agents/Sage.toml +1 -2
- package/.codex/agents/Vibe.toml +1 -2
- package/.codex/hooks.json +24 -13
- package/.codex/prompts/enforcement.md +4 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +4 -0
- package/COVERAGE.md +211 -0
- package/DEMO.md +274 -0
- package/LICENSE +0 -0
- package/README.md +132 -656
- package/bin/ma.js +748 -55
- package/data/clone-data.ledger.json +41 -0
- package/data/clone-data.proof.json +50 -0
- package/data/clone-data.rvf +37 -0
- package/docs/README.md +10 -1
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +62 -22
- package/docs/mcp-setup.md +64 -0
- package/docs/qa/release-issue-gates-0.14.0.json +644 -0
- package/docs/qa/release-readiness-0.1.5.md +1 -1
- package/docs/qa/{release-readiness-0.1.12.md → release-readiness-0.14.0.md} +25 -9
- package/docs/quality.md +24 -0
- package/docs/reference/native-security-playbooks.md +6 -0
- package/docs/reference/native-source-selection.md +6 -0
- package/docs/release-spec.md +39 -10
- package/docs/skills.md +14 -1
- package/index.js +295 -2
- package/mcp/collections.json +23 -6
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +179 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +56 -4
- package/mcp/local-capabilities.json +9 -0
- package/mcp/servers.json +24 -0
- package/package.json +29 -4
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +8 -1
- package/plugins/meta-architect/obsidian/main.js +534 -0
- package/plugins/meta-architect/obsidian/manifest.json +9 -0
- package/plugins/meta-architect/obsidian/styles.css +7 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +2 -0
- package/plugins/meta-architect/skills/build/SKILL.md +3 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +8 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +2 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +7 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
- package/schemas/autonomous-task-queue.schema.json +56 -0
- package/schemas/handoff-packet.schema.json +36 -0
- package/schemas/setup-receipt.schema.json +28 -0
- package/schemas/skill-frontmatter.schema.json +20 -0
- package/schemas/support-bundle.schema.json +37 -0
- package/schemas/task-contract.schema.json +31 -0
- package/scripts/active-autonomy-hook.mjs +226 -0
- package/scripts/build-linux-packages.mjs +7 -3
- package/scripts/cleanup-test-fixtures.sh +55 -0
- package/scripts/context-hydration-hook.mjs +89 -0
- package/scripts/demo-smoke.js +285 -0
- package/scripts/doctor.js +27 -12
- package/scripts/install.sh +57 -0
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/postinstall.js +17 -10
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +35 -0
- package/scripts/release-sync.js +72 -7
- package/scripts/release-verify.js +260 -2
- package/scripts/setup-npmrc.js +48 -23
- package/scripts/skills-install.js +9 -2
- package/scripts/skills-manifest.js +2 -21
- package/scripts/skills-pack.js +2 -2
- package/scripts/skills-validate.js +5 -55
- package/skills/arch/SKILL.md +2 -0
- package/skills/build/SKILL.md +3 -0
- package/skills/flow/SKILL.md +2 -0
- package/skills/index.json +0 -0
- package/skills/sage/SKILL.md +2 -0
- package/skills/sage/references/source-selection.md +8 -0
- package/skills/vet/SKILL.md +2 -0
- package/skills/vet/references/security-playbooks.md +7 -0
- package/skills/vibe/SKILL.md +2 -0
- package/sprint/00-idea.md +0 -0
- package/sprint/01-architecture.md +0 -0
- package/sprint/02-oss-evidence.md +0 -0
- package/sprint/03-logic.md +0 -0
- package/sprint/04-security.md +0 -0
- package/sprint/05-dx-ux.md +0 -0
- package/sprint/06-build-plan.md +0 -0
- package/sprint/07-release.md +0 -0
- package/src/agents.js +279 -0
- package/src/bootstrap.js +268 -62
- package/src/build-gate.js +2 -2
- package/src/codex-app-server.js +291 -0
- package/src/decision-log.js +0 -0
- package/src/fs-utils.js +175 -4
- package/src/launcher.js +28 -21
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +422 -3
- package/src/paths.js +10 -1
- package/src/policy.js +3 -3
- package/src/prelaunch.js +189 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +252 -0
- package/src/release-operations.js +62 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +208 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +165 -0
- package/src/runtime/architect-review.js +326 -0
- package/src/runtime/autonomous-tasks.js +408 -0
- package/src/runtime/build-readiness.js +28 -4
- package/src/runtime/code-graph-rehearse.js +113 -0
- package/src/runtime/codeburn-core.js +112 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +316 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +379 -0
- package/src/runtime/detached-provider.js +142 -0
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +460 -0
- package/src/runtime/exposure-catalog.js +276 -0
- package/src/runtime/graphify-core.js +167 -0
- package/src/runtime/guidance-stack.js +9 -2
- package/src/runtime/handoff-packets.js +92 -0
- package/src/runtime/headroom-core.js +104 -0
- package/src/runtime/helper-orchestration-core.js +307 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +344 -0
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +18 -0
- package/src/runtime/maestro-manager.js +19 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +125 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +192 -0
- package/src/runtime/obsidian-integration-core.js +1089 -0
- package/src/runtime/obsidian-plugin-bridge.js +1045 -0
- package/src/runtime/pi-maestro-core.js +89 -0
- package/src/runtime/preferences.js +79 -0
- package/src/runtime/project-context.js +636 -0
- package/src/runtime/prompt-strategy-core.js +230 -0
- package/src/runtime/quorum-review.js +90 -0
- package/src/runtime/ralph-execution-core.js +217 -0
- package/src/runtime/redaction-gateway.js +373 -0
- package/src/runtime/runtime-state.js +935 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +147 -0
- package/src/runtime/signal-hooks.js +28 -0
- package/src/runtime/skills-registry-export.js +754 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +898 -0
- package/src/runtime/workspace-intelligence-runtime.js +674 -0
- package/src/runtime/workspace-virtualizer.js +102 -0
- package/src/runtime-artifacts.js +225 -62
- package/src/setup-lifecycle.js +359 -0
- package/src/skill-frontmatter.js +78 -0
- package/src/skill-installer.js +226 -71
- package/src/skills.js +1166 -96
- package/src/state-sync.js +0 -0
- package/src/test-fixtures.js +543 -0
- package/src/tui/grid.js +67 -0
- package/src/tui/status-grid.js +23 -0
- package/support-bundle.json +114 -0
- package/templates/AGENTS.md +6 -2
- package/templates/catalog-manifest.json +0 -0
- package/templates/model-instructions/core.md +0 -0
- package/templates/model-instructions/release.md +0 -0
- package/templates/model-instructions/security.md +0 -0
- package/templates/quality/ai-quality-rules.yml +37 -0
- package/docs/assets/image/Screenshot(1).png +0 -0
- package/docs/assets/image/Screenshot(2).png +0 -0
- package/docs/assets/image/Screenshot(3).png +0 -0
- package/docs/assets/image/Screenshot(4).png +0 -0
- package/docs/assets/image/Screenshot(5).png +0 -0
- package/docs/assets/image/Screenshot(6).png +0 -0
- package/docs/assets/image/Screenshot(7).png +0 -0
- package/docs/assets/image/Screenshot(8).png +0 -0
- package/docs/assets/image/Screenshot(9).png +0 -0
- package/docs/assets/meta-architect-logo.png +0 -0
- package/docs/assets/meta-architect-logo.svg +0 -8
- package/docs/onboarding.md +0 -65
- package/docs/skills-publishing.md +0 -255
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
|
|
5
|
+
import { getRuntimeStatePath } from "../paths.js";
|
|
6
|
+
import { appendMaestroEvent } from "./maestro-events.js";
|
|
7
|
+
|
|
8
|
+
const redactionRules = [
|
|
9
|
+
{
|
|
10
|
+
placeholderBase: "__MA_SECURE_CONN_DB__",
|
|
11
|
+
regex: /postgres:\/\/[^\s"'`]+/gi,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
placeholderBase: "__MA_SECURE_CRYPTO_KEY__",
|
|
15
|
+
regex: /-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----/g,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
placeholderBase: "__MA_ANONYMOUS_IDENTITY__",
|
|
19
|
+
regex: /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
placeholderBase: "__MA_SECURE_TOKEN__",
|
|
23
|
+
regex:
|
|
24
|
+
/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9_]{16,}|xox[baprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z-_]{20,})\b/g,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
placeholderBase: "__MA_SECURE_ASSIGNMENT__",
|
|
28
|
+
regex:
|
|
29
|
+
/\b(?:API_KEY|API_TOKEN|AUTH_TOKEN|SECRET_KEY|DATABASE_URL|PRIVATE_KEY|PASSWORD)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export function getRedactionVaultPath() {
|
|
34
|
+
return getRuntimeStatePath("redaction-vault.json");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createDefaultVault() {
|
|
38
|
+
return {
|
|
39
|
+
schemaVersion: "0.1.0",
|
|
40
|
+
entries: {},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getRedactionVaultPolicy() {
|
|
45
|
+
const retentionDays = Number.parseInt(process.env.MA_REDACTION_VAULT_RETENTION_DAYS ?? "30", 10);
|
|
46
|
+
const maxEntries = Number.parseInt(process.env.MA_REDACTION_VAULT_MAX_ENTRIES ?? "200", 10);
|
|
47
|
+
return {
|
|
48
|
+
retentionDays: Number.isFinite(retentionDays) ? Math.min(Math.max(retentionDays, 1), 3650) : 30,
|
|
49
|
+
maxEntries: Number.isFinite(maxEntries) ? Math.min(Math.max(maxEntries, 1), 10000) : 200,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function loadRedactionVaultOrDefault() {
|
|
54
|
+
try {
|
|
55
|
+
const vault = await readJson(getRedactionVaultPath());
|
|
56
|
+
const normalized = applyRedactionVaultPolicy(vault);
|
|
57
|
+
if (JSON.stringify(vault) !== JSON.stringify(normalized)) {
|
|
58
|
+
await saveRedactionVault(normalized);
|
|
59
|
+
}
|
|
60
|
+
return normalized;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
63
|
+
return createDefaultVault();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function seedRedactionVault() {
|
|
71
|
+
const vaultPath = getRedactionVaultPath();
|
|
72
|
+
await writeFileIfMissing(
|
|
73
|
+
vaultPath,
|
|
74
|
+
`${JSON.stringify(applyRedactionVaultPolicy(createDefaultVault()), null, 2)}\n`,
|
|
75
|
+
);
|
|
76
|
+
await secureRedactionVaultPermissions(vaultPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function saveRedactionVault(vault) {
|
|
80
|
+
const normalized = applyRedactionVaultPolicy(vault);
|
|
81
|
+
const vaultPath = getRedactionVaultPath();
|
|
82
|
+
await writeJson(vaultPath, normalized);
|
|
83
|
+
await secureRedactionVaultPermissions(vaultPath);
|
|
84
|
+
return normalized;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function secureRedactionVaultPermissions(vaultPath) {
|
|
88
|
+
const directory = path.dirname(vaultPath);
|
|
89
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
90
|
+
await fs.chmod(directory, 0o700);
|
|
91
|
+
await fs.chmod(vaultPath, 0o600);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function inspectRedactionVault() {
|
|
95
|
+
const vaultPath = getRedactionVaultPath();
|
|
96
|
+
try {
|
|
97
|
+
const [file, directory] = await Promise.all([
|
|
98
|
+
fs.stat(vaultPath),
|
|
99
|
+
fs.stat(path.dirname(vaultPath)),
|
|
100
|
+
]);
|
|
101
|
+
const fileMode = file.mode & 0o777;
|
|
102
|
+
const directoryMode = directory.mode & 0o777;
|
|
103
|
+
const issues = [];
|
|
104
|
+
if (fileMode !== 0o600) issues.push(`vault file mode ${fileMode.toString(8)}; expected 600`);
|
|
105
|
+
if (directoryMode !== 0o700) {
|
|
106
|
+
issues.push(`vault directory mode ${directoryMode.toString(8)}; expected 700`);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
exists: true,
|
|
110
|
+
path: vaultPath,
|
|
111
|
+
status: issues.length > 0 ? "warning" : "ok",
|
|
112
|
+
issues,
|
|
113
|
+
policy: getRedactionVaultPolicy(),
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error?.code === "ENOENT") {
|
|
117
|
+
return {
|
|
118
|
+
exists: false,
|
|
119
|
+
path: vaultPath,
|
|
120
|
+
status: "ok",
|
|
121
|
+
issues: [],
|
|
122
|
+
policy: getRedactionVaultPolicy(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function purgeRedactionVault({ dryRun = false } = {}) {
|
|
130
|
+
const vaultPath = getRedactionVaultPath();
|
|
131
|
+
const exists = await fs
|
|
132
|
+
.stat(vaultPath)
|
|
133
|
+
.then(() => true)
|
|
134
|
+
.catch((error) => {
|
|
135
|
+
if (error?.code === "ENOENT") return false;
|
|
136
|
+
throw error;
|
|
137
|
+
});
|
|
138
|
+
if (exists && !dryRun) await fs.rm(vaultPath, { force: true });
|
|
139
|
+
return { status: exists ? (dryRun ? "would-purge" : "purged") : "absent", path: vaultPath };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function appendRedactionEvent({ replacementCount, metadata }) {
|
|
143
|
+
await appendMaestroEvent({
|
|
144
|
+
record_type: "privacy:redaction_applied",
|
|
145
|
+
gate: metadata.gate ?? "$maestro",
|
|
146
|
+
target: metadata.kind ?? "provider-bound-context",
|
|
147
|
+
replacement_count: replacementCount,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function maskSensitiveTextInternal(rawText) {
|
|
152
|
+
let sanitizedText = rawText;
|
|
153
|
+
const replacements = [];
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
|
|
156
|
+
for (const rule of redactionRules) {
|
|
157
|
+
sanitizedText = sanitizedText.replace(rule.regex, (match) => {
|
|
158
|
+
const digest = createHash("sha256").update(`${rule.placeholderBase}:${match}`).digest("hex");
|
|
159
|
+
const placeholder = `${rule.placeholderBase}_${digest.slice(0, 12)}__`;
|
|
160
|
+
if (!seen.has(placeholder)) {
|
|
161
|
+
replacements.push({
|
|
162
|
+
placeholder,
|
|
163
|
+
rawValue: match,
|
|
164
|
+
placeholderBase: rule.placeholderBase,
|
|
165
|
+
});
|
|
166
|
+
seen.add(placeholder);
|
|
167
|
+
}
|
|
168
|
+
return placeholder;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
sanitizedText: redactPreviewPaths(sanitizedText),
|
|
174
|
+
replacements,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function maskSensitiveText(rawText) {
|
|
179
|
+
const result = maskSensitiveTextInternal(rawText);
|
|
180
|
+
return {
|
|
181
|
+
sanitizedText: result.sanitizedText,
|
|
182
|
+
replacements: result.replacements.map(publicReplacement),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function redactProviderBoundText(rawText, metadata = {}) {
|
|
187
|
+
const vault = await loadRedactionVaultOrDefault();
|
|
188
|
+
const { sanitizedText, replacements } = maskSensitiveTextInternal(rawText);
|
|
189
|
+
|
|
190
|
+
if (replacements.length === 0) {
|
|
191
|
+
return {
|
|
192
|
+
sanitizedText,
|
|
193
|
+
replacements: replacements.map(publicReplacement),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const replacement of replacements) {
|
|
198
|
+
vault.entries[replacement.placeholder] = {
|
|
199
|
+
rawValue: replacement.rawValue,
|
|
200
|
+
placeholderBase: replacement.placeholderBase,
|
|
201
|
+
createdAt: new Date().toISOString(),
|
|
202
|
+
metadata,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await saveRedactionVault(vault);
|
|
207
|
+
await appendRedactionEvent({ replacementCount: replacements.length, metadata });
|
|
208
|
+
return {
|
|
209
|
+
sanitizedText,
|
|
210
|
+
replacements: replacements.map(publicReplacement),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function redactPayloadValue(value, replacements, seen = new WeakSet()) {
|
|
215
|
+
if (typeof value === "string") {
|
|
216
|
+
const result = maskSensitiveTextInternal(value);
|
|
217
|
+
replacements.push(...result.replacements);
|
|
218
|
+
return redactPreviewPaths(result.sanitizedText);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (Array.isArray(value)) {
|
|
222
|
+
return redactObjectValue(value, replacements, seen);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (value && typeof value === "object") {
|
|
226
|
+
return redactObjectValue(value, replacements, seen);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function filterPublicValue(value, classes, seen = new WeakSet()) {
|
|
233
|
+
if (typeof value === "string") {
|
|
234
|
+
if (/(?:[A-Za-z]:\\|\/(?:Users|home|tmp|var|etc|opt|srv|mnt|private)[\\/])/.test(value)) {
|
|
235
|
+
classes.add("local_path");
|
|
236
|
+
}
|
|
237
|
+
return { value, omitted: false };
|
|
238
|
+
}
|
|
239
|
+
if (!value || typeof value !== "object") return { value, omitted: false };
|
|
240
|
+
if (
|
|
241
|
+
["personal/local", "user-local", "session-only"].includes(value.scope) ||
|
|
242
|
+
(value.freshness?.status === "stale" && !value.provenance && !value.sourceFiles)
|
|
243
|
+
) {
|
|
244
|
+
classes.add(value.scope ? "private_context" : "stale_without_provenance");
|
|
245
|
+
return { value: undefined, omitted: true };
|
|
246
|
+
}
|
|
247
|
+
if (seen.has(value)) return { value: "[Circular]", omitted: false };
|
|
248
|
+
seen.add(value);
|
|
249
|
+
if (Array.isArray(value)) {
|
|
250
|
+
const result = value
|
|
251
|
+
.map((item) => filterPublicValue(item, classes, seen))
|
|
252
|
+
.filter((item) => !item.omitted)
|
|
253
|
+
.map((item) => item.value);
|
|
254
|
+
seen.delete(value);
|
|
255
|
+
return { value: result, omitted: false };
|
|
256
|
+
}
|
|
257
|
+
const result = {};
|
|
258
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
259
|
+
const filtered = filterPublicValue(entry, classes, seen);
|
|
260
|
+
if (!filtered.omitted) result[key] = filtered.value;
|
|
261
|
+
}
|
|
262
|
+
seen.delete(value);
|
|
263
|
+
return { value: result, omitted: false };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function redactObjectValue(value, replacements, seen) {
|
|
267
|
+
if (!value || typeof value !== "object") {
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
if (seen.has(value)) {
|
|
271
|
+
return "[Circular]";
|
|
272
|
+
}
|
|
273
|
+
seen.add(value);
|
|
274
|
+
|
|
275
|
+
if (Array.isArray(value)) {
|
|
276
|
+
const result = value.map((item) =>
|
|
277
|
+
typeof item === "string"
|
|
278
|
+
? redactPayloadValue(item, replacements, seen)
|
|
279
|
+
: redactObjectValue(item, replacements, seen),
|
|
280
|
+
);
|
|
281
|
+
seen.delete(value);
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const result = Object.fromEntries(
|
|
286
|
+
Object.entries(value).map(([key, entry]) => {
|
|
287
|
+
const redactedEntry =
|
|
288
|
+
typeof entry === "string"
|
|
289
|
+
? redactPayloadValue(entry, replacements, seen)
|
|
290
|
+
: redactObjectValue(entry, replacements, seen);
|
|
291
|
+
if (typeof redactedEntry === "string" && /(?:preview|excerpt|snippet|path|file)/i.test(key)) {
|
|
292
|
+
return [key, redactPreviewPaths(redactedEntry)];
|
|
293
|
+
}
|
|
294
|
+
return [key, redactedEntry];
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
297
|
+
seen.delete(value);
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function publicReplacement({ placeholder, placeholderBase }) {
|
|
302
|
+
return { placeholder, placeholderBase };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function redactProviderBoundPayload(payload, metadata = {}) {
|
|
306
|
+
const vault = await loadRedactionVaultOrDefault();
|
|
307
|
+
const replacements = [];
|
|
308
|
+
const classes = new Set();
|
|
309
|
+
const filtered = filterPublicValue(payload, classes).value;
|
|
310
|
+
const sanitizedPayload = redactPayloadValue(filtered, replacements);
|
|
311
|
+
|
|
312
|
+
for (const replacement of replacements) {
|
|
313
|
+
vault.entries[replacement.placeholder] = {
|
|
314
|
+
rawValue: replacement.rawValue,
|
|
315
|
+
placeholderBase: replacement.placeholderBase,
|
|
316
|
+
createdAt: new Date().toISOString(),
|
|
317
|
+
metadata,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (replacements.length > 0) {
|
|
322
|
+
await saveRedactionVault(vault);
|
|
323
|
+
await appendRedactionEvent({ replacementCount: replacements.length, metadata });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
sanitizedPayload,
|
|
328
|
+
replacements: replacements.map(publicReplacement),
|
|
329
|
+
redaction_receipt: {
|
|
330
|
+
record_type: "redaction_receipt",
|
|
331
|
+
replacement_count: replacements.length,
|
|
332
|
+
provider_safe: true,
|
|
333
|
+
raw_values_stored_locally: replacements.length > 0,
|
|
334
|
+
redacted_classes: [
|
|
335
|
+
...classes,
|
|
336
|
+
...new Set(replacements.map((replacement) => replacement.placeholderBase)),
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function redactPreviewPaths(value) {
|
|
343
|
+
return value.replace(
|
|
344
|
+
/(?:[A-Za-z]:\\|\/(?:Users|home|tmp|var|etc|opt|srv|mnt|private)[\\/])(?:[A-Za-z0-9._ -]+[\\/])+[A-Za-z0-9._ -]+(?:\.[A-Za-z0-9._ -]+)?/g,
|
|
345
|
+
"__MA_PATH_REDACTED__",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function applyRedactionVaultPolicy(vault) {
|
|
350
|
+
const entries = Object.entries(vault?.entries ?? {});
|
|
351
|
+
entries.sort((left, right) => {
|
|
352
|
+
const leftTime = Date.parse(left[1]?.createdAt ?? "") || 0;
|
|
353
|
+
const rightTime = Date.parse(right[1]?.createdAt ?? "") || 0;
|
|
354
|
+
return rightTime - leftTime;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const policy = getRedactionVaultPolicy();
|
|
358
|
+
const cutoff = Date.now() - policy.retentionDays * 24 * 60 * 60 * 1000;
|
|
359
|
+
const retainedEntries = entries.filter(([, entry]) => {
|
|
360
|
+
const createdAt = Date.parse(entry?.createdAt ?? "") || 0;
|
|
361
|
+
return createdAt >= cutoff;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const limitedEntries = retainedEntries.slice(0, policy.maxEntries);
|
|
365
|
+
return {
|
|
366
|
+
...vault,
|
|
367
|
+
policy: {
|
|
368
|
+
retention_days: policy.retentionDays,
|
|
369
|
+
max_entries: policy.maxEntries,
|
|
370
|
+
},
|
|
371
|
+
entries: Object.fromEntries(limitedEntries),
|
|
372
|
+
};
|
|
373
|
+
}
|