@tangle-network/agent-profile-materialize 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -28,6 +28,26 @@ export interface PlanFile {
28
28
  content: string;
29
29
  /** chmod mode (e.g. 0o755 for hook scripts); default 0o644. */
30
30
  mode?: number;
31
+ /**
32
+ * Provenance of this entry. `"generated"` = the materializer synthesized the
33
+ * content itself (native harness config, merged settings, lowered prompt/MCP/
34
+ * hooks/subagents). `"user"` = the exact bytes of a profile-declared resource
35
+ * ref (`resources.files`/`tools`/`agents`/`skills`/`commands`), passed through
36
+ * verbatim at the path the profile author chose.
37
+ *
38
+ * A consumer that needs to reinterpret a SPECIFIC generated file (e.g. parse
39
+ * `opencode.json` as the synthetic native config) MUST gate on
40
+ * `source === "generated"` at that relPath — matching by relPath alone cannot
41
+ * tell a harness-authored config apart from a user resource mount that
42
+ * happens to share the same conventional path, and treating arbitrary user
43
+ * content as trusted generated config is a fail-open bug, not a fallback.
44
+ *
45
+ * Optional: callers that hand-construct a {@link WorkspacePlan} directly for
46
+ * {@link applyWorkspacePlan} (tests, ad hoc writes) are not materializer
47
+ * output and have no provenance to report. `materializeProfile` always sets
48
+ * this on every file it emits.
49
+ */
50
+ source?: "user" | "generated";
31
51
  }
32
52
  export interface Unsupported {
33
53
  dimension: "files" | "skills" | "mcp" | "hooks" | "subagents" | "commands" | "instructions" | "systemPrompt" | "connections" | "confidential" | "permissions" | "tools" | "modes" | "extensions";
package/dist/index.js CHANGED
@@ -25,15 +25,32 @@ import { createHash, randomUUID } from "node:crypto";
25
25
  import { closeSync, fchmodSync, constants as fsConstants, fstatSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
26
26
  import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32, } from "node:path";
27
27
  import { agentCandidateProfileActivationSchema, agentCandidateProfilePlanEvidenceSchema, agentCandidateProfilePlanMaterialSchema, agentCandidateProfileSchema, agentProfileSchema, canonicalCandidateBytes, canonicalCandidateDigest, resolveNativeWebTools, } from "@tangle-network/agent-interface";
28
+ function describeUnresolvedRef(r) {
29
+ const name = typeof r.name === "string" ? r.name : undefined;
30
+ const path = typeof r.path === "string" ? r.path : undefined;
31
+ if (name && path)
32
+ return `"${name}" (${path})`;
33
+ if (name)
34
+ return `"${name}"`;
35
+ if (path)
36
+ return `"${path}"`;
37
+ return "resource";
38
+ }
28
39
  /** Resolve inline bytes already present on the profile. Remote resources must
29
- * be resolved by {@link resolveAgentProfileResources} before materialization. */
40
+ * be resolved by {@link resolveAgentProfileResources} before materialization.
41
+ *
42
+ * FAIL-CLOSED: a ref that declares `kind: "inline"` is asserting its bytes are
43
+ * already resolved, so missing/non-string `content` is a caller bug, not an
44
+ * empty file — silently coercing it (`String(content ?? "")`) would produce a
45
+ * file that "looks provisioned, loads nothing", exactly the trap this module's
46
+ * docstring warns against. */
30
47
  function resolveInlineRef(ref) {
31
48
  const r = ref;
32
- if (r.kind === "inline" || typeof r.content === "string") {
33
- return {
34
- name: String(r.name ?? r.path ?? "resource"),
35
- content: String(r.content ?? ""),
36
- };
49
+ if (typeof r.content === "string") {
50
+ return { name: String(r.name ?? r.path ?? "resource"), content: r.content };
51
+ }
52
+ if (r.kind === "inline") {
53
+ throw new Error(`Profile resource ${describeUnresolvedRef(r)} declares kind "inline" but has no string content`);
37
54
  }
38
55
  return null; // github / unresolved
39
56
  }
@@ -424,14 +441,23 @@ export function materializeProfile(inputProfile, harness, opts = {}) {
424
441
  flags: [],
425
442
  unsupported: [],
426
443
  };
427
- const add = (relPath, content, mode) => {
444
+ // Every plan file is tagged by provenance at the single push site: `add`
445
+ // (harness-generated native config) vs `addExplicitResource` (a profile
446
+ // resource ref's bytes, passed through verbatim). Downstream consumers that
447
+ // reinterpret a specific generated file by relPath (e.g. opencode.json as
448
+ // the synthetic native config) must gate on `source === "generated"` —
449
+ // otherwise a user resource mounted at the same conventional path is
450
+ // silently misread as trusted harness config.
451
+ const pushPlanFile = (relPath, content, source, mode) => {
428
452
  const normalizedPath = normalizeWorkspaceRelativePath(relPath);
429
453
  plan.files.push({
430
454
  relPath: normalizedPath,
431
455
  content,
456
+ source,
432
457
  ...(mode === undefined ? {} : { mode: validateFileMode(mode) }),
433
458
  });
434
459
  };
460
+ const add = (relPath, content, mode) => pushPlanFile(relPath, content, "generated", mode);
435
461
  const unsupported = (dimension, reason) => plan.unsupported.push({ dimension, reason });
436
462
  const explicitResourcePaths = new Map();
437
463
  const addExplicitResource = (dimension, relPath, content, mode) => {
@@ -441,7 +467,7 @@ export function materializeProfile(inputProfile, harness, opts = {}) {
441
467
  throw new Error(`Duplicate profile resource path: ${normalizedPath} (${previousDimension}, ${dimension})`);
442
468
  }
443
469
  explicitResourcePaths.set(normalizedPath, dimension);
444
- add(normalizedPath, content, mode);
470
+ pushPlanFile(normalizedPath, content, "user", mode);
445
471
  };
446
472
  // 1) GENERIC FILES → exact workspace-relative paths. Inline refs are
447
473
  // deterministic; remote refs must be resolved by an async caller first.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-profile-materialize",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-dev-container, and VB).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@tangle-network/agent-interface": "0.31.0"
19
+ "@tangle-network/agent-interface": "0.33.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "25.6.0",