@sema-agent/core 7.50.2 → 7.51.1

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.
Files changed (200) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/dist/agents/child-failure.d.ts +5 -16
  3. package/dist/agents/child-failure.js +4 -24
  4. package/dist/agents/subagent-steps.js +2 -2
  5. package/dist/agents/subagent.d.ts +4 -13
  6. package/dist/agents/subagent.js +94 -204
  7. package/dist/agents/tool-filter.d.ts +2 -2
  8. package/dist/brain/anthropic.d.ts +1 -1
  9. package/dist/brain/anthropic.js +7 -5
  10. package/dist/brain/open-responses.d.ts +1 -1
  11. package/dist/brain/open-responses.js +3 -2
  12. package/dist/brain/openai.d.ts +1 -1
  13. package/dist/brain/openai.js +3 -2
  14. package/dist/brain/request-route.d.ts +2 -2
  15. package/dist/brain/route-adjudicator.d.ts +16 -7
  16. package/dist/brain/route-adjudicator.js +23 -7
  17. package/dist/brain/route-conformance.js +23 -5
  18. package/dist/brain/stream-engine.js +9 -3
  19. package/dist/core/a2a.js +6 -8
  20. package/dist/core/allow-entry.d.ts +45 -26
  21. package/dist/core/allow-entry.js +48 -24
  22. package/dist/core/arg-summary.d.ts +8 -85
  23. package/dist/core/arg-summary.js +4 -386
  24. package/dist/core/ask-origin.d.ts +35 -33
  25. package/dist/core/ask-origin.js +9 -11
  26. package/dist/core/checkpoint-requirements.d.ts +6 -0
  27. package/dist/core/checkpoint-requirements.js +1 -0
  28. package/dist/core/checkpoint-store.d.ts +15 -34
  29. package/dist/core/checkpoint-store.js +5 -4
  30. package/dist/core/classifier-unavailable-arm.d.ts +12 -8
  31. package/dist/core/classifier-unavailable-arm.js +7 -2
  32. package/dist/core/credential-vocabulary.d.ts +3 -2
  33. package/dist/core/credential-vocabulary.js +27 -18
  34. package/dist/core/delegation-frames.d.ts +40 -15
  35. package/dist/core/delegation-frames.js +34 -0
  36. package/dist/core/engine-capabilities.d.ts +15 -0
  37. package/dist/core/engine-capabilities.js +1 -0
  38. package/dist/core/engine-notice.d.ts +41 -14
  39. package/dist/core/gate-fold.js +9 -7
  40. package/dist/core/gate-lanes.js +6 -4
  41. package/dist/core/gate-outcome.d.ts +81 -15
  42. package/dist/core/gate-outcome.js +168 -67
  43. package/dist/core/governance-codes.d.ts +7 -1
  44. package/dist/core/governance-codes.js +9 -0
  45. package/dist/core/hooks.d.ts +4 -4
  46. package/dist/core/hooks.js +4 -3
  47. package/dist/core/mcp-connection-pool.d.ts +2 -0
  48. package/dist/core/mcp-connection-pool.js +2 -1
  49. package/dist/core/mcp-failure.js +2 -1
  50. package/dist/core/mcp-server-spec.d.ts +27 -5
  51. package/dist/core/mcp.d.ts +13 -38
  52. package/dist/core/mcp.js +28 -23
  53. package/dist/core/media-byte-cap.d.ts +25 -0
  54. package/dist/core/media-byte-cap.js +18 -2
  55. package/dist/core/memory-engine/consolidation-driver.js +1 -1
  56. package/dist/core/mode-policy.d.ts +25 -1
  57. package/dist/core/mode-policy.js +28 -13
  58. package/dist/core/own-property-receipt.d.ts +18 -0
  59. package/dist/core/own-property-receipt.js +39 -0
  60. package/dist/core/permission-rule-model.d.ts +32 -11
  61. package/dist/core/permission-rule-model.js +12 -7
  62. package/dist/core/persisted-rule-arms.d.ts +3 -3
  63. package/dist/core/prompt-section-seat.d.ts +54 -0
  64. package/dist/core/prompt-section-seat.js +49 -0
  65. package/dist/core/protocol-http-auth.d.ts +44 -0
  66. package/dist/core/protocol-http-auth.js +45 -0
  67. package/dist/core/read-face-seats.d.ts +30 -0
  68. package/dist/core/read-face-seats.js +38 -0
  69. package/dist/core/redaction-passes.d.ts +82 -0
  70. package/dist/core/redaction-passes.js +378 -0
  71. package/dist/core/remote-env.d.ts +4 -3
  72. package/dist/core/resume-startup-cause.d.ts +56 -0
  73. package/dist/core/resume-startup-cause.js +43 -0
  74. package/dist/core/rule-denial-sentence.d.ts +2 -2
  75. package/dist/core/runner/announce-once-ledger.js +2 -2
  76. package/dist/core/runner/classifier-ladder.d.ts +17 -13
  77. package/dist/core/runner/classifier-ladder.js +12 -10
  78. package/dist/core/runner/construction-doors.js +9 -20
  79. package/dist/core/runner/contracts.d.ts +4 -4
  80. package/dist/core/runner/gate-exit.js +2 -2
  81. package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
  82. package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
  83. package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
  84. package/dist/core/runner/person-path-rows.d.ts +4 -3
  85. package/dist/core/runner/policy-throw.js +4 -4
  86. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  87. package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
  88. package/dist/core/runner/prepare-gate-stations.js +4 -4
  89. package/dist/core/runner/prepare-hands-readface.js +1 -19
  90. package/dist/core/runner/prepare-inherited-gate.js +10 -9
  91. package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
  92. package/dist/core/runner/prepare-policy-chain.js +10 -13
  93. package/dist/core/runner/prepare-project-context.d.ts +4 -4
  94. package/dist/core/runner/prepare-project-context.js +2 -1
  95. package/dist/core/runner/prepare-prompt-assembly.js +1 -0
  96. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
  97. package/dist/core/runner/prepare-prompt-inputs.js +4 -4
  98. package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
  99. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  100. package/dist/core/runner/prepare-task.js +6 -4
  101. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
  102. package/dist/core/runner/prepare-turn-wiring.js +8 -7
  103. package/dist/core/runner/run-harness-handlers.js +2 -2
  104. package/dist/core/runner/run-leg.js +10 -9
  105. package/dist/core/runner/run-settle-and-teardown.js +5 -5
  106. package/dist/core/runner/run-turn-boundary.js +3 -2
  107. package/dist/core/runner/runtask.d.ts +1 -2
  108. package/dist/core/runner/runtask.js +8 -12
  109. package/dist/core/runner/session-rule-policy.d.ts +15 -11
  110. package/dist/core/runner/session-rule-policy.js +43 -33
  111. package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
  112. package/dist/core/runner/stream-settle-backstop.js +2 -1
  113. package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
  114. package/dist/core/runner/sub-agent-deny-lists.js +23 -12
  115. package/dist/core/runner/terminal-account.js +3 -3
  116. package/dist/core/runner/tool-output-projection.d.ts +5 -0
  117. package/dist/core/runner/tool-output-projection.js +6 -11
  118. package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
  119. package/dist/core/runner/tool-result-media-seat.js +13 -0
  120. package/dist/core/runner/turn-attachments.d.ts +13 -12
  121. package/dist/core/runner/turn-attachments.js +11 -7
  122. package/dist/core/runner-deps.d.ts +4 -3
  123. package/dist/core/safety-merge-corpus.js +21 -21
  124. package/dist/core/secret-env.d.ts +41 -15
  125. package/dist/core/secret-env.js +85 -19
  126. package/dist/core/session-policy-store.d.ts +89 -72
  127. package/dist/core/session-policy-store.js +100 -93
  128. package/dist/core/shell-read-silence.d.ts +38 -4
  129. package/dist/core/shell-read-silence.js +102 -0
  130. package/dist/core/side-query.d.ts +7 -8
  131. package/dist/core/skill-grant-binding.d.ts +32 -11
  132. package/dist/core/skill-grant-binding.js +11 -3
  133. package/dist/core/skill-grant.d.ts +4 -3
  134. package/dist/core/skill-grant.js +5 -4
  135. package/dist/core/skills-directory.d.ts +12 -1
  136. package/dist/core/skills-directory.js +17 -8
  137. package/dist/core/spec-contract.d.ts +5 -3
  138. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  139. package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
  140. package/dist/core/swappable-deps.d.ts +15 -1
  141. package/dist/core/swappable-deps.js +25 -1
  142. package/dist/core/synthetic-read-verdict.d.ts +16 -14
  143. package/dist/core/synthetic-read-verdict.js +28 -8
  144. package/dist/core/task-notification.d.ts +13 -3
  145. package/dist/core/task-notification.js +12 -1
  146. package/dist/core/task-spec.d.ts +14 -15
  147. package/dist/core/tool-name-readings.d.ts +0 -4
  148. package/dist/core/tool-name-readings.js +0 -1
  149. package/dist/core/tool-policy.d.ts +40 -9
  150. package/dist/core/tool-policy.js +25 -7
  151. package/dist/core/types.d.ts +3 -3
  152. package/dist/core/types.js +1 -1
  153. package/dist/core/untrusted-egress.d.ts +60 -11
  154. package/dist/core/untrusted-egress.js +120 -9
  155. package/dist/core/untrusted-text.js +16 -0
  156. package/dist/core/wiring-manifest.d.ts +22 -8
  157. package/dist/core/wiring-manifest.js +12 -4
  158. package/dist/engine/execution-env/kill-tree.js +4 -4
  159. package/dist/engine/execution-env/node-execution-env.d.ts +11 -4
  160. package/dist/engine/execution-env/node-execution-env.js +9 -4
  161. package/dist/engine/harness/agent-harness.d.ts +2 -1
  162. package/dist/engine/harness/agent-harness.js +4 -1
  163. package/dist/engine/harness/types.d.ts +3 -0
  164. package/dist/engine/llm/types.d.ts +7 -9
  165. package/dist/engine/loop/agent-loop.d.ts +8 -3
  166. package/dist/engine/loop/agent-loop.js +26 -9
  167. package/dist/engine/loop/types.d.ts +10 -0
  168. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -1
  169. package/dist/engine/lsp/node-lsp-manager.js +2 -2
  170. package/dist/index.d.ts +22 -5
  171. package/dist/index.js +18 -5
  172. package/dist/orchestration/run-workflow-tool.js +8 -8
  173. package/dist/orchestration/workflow.js +2 -2
  174. package/dist/prompt-assembly/assemble.d.ts +3 -1
  175. package/dist/prompt-assembly/assemble.js +6 -0
  176. package/dist/prompts/cowork.d.ts +42 -0
  177. package/dist/prompts/cowork.js +529 -0
  178. package/dist/prompts/default.d.ts +14 -1
  179. package/dist/prompts/default.js +3 -0
  180. package/dist/stores/file/fs-atomic.js +2 -2
  181. package/dist/stores/file/session-policy-store.d.ts +6 -3
  182. package/dist/stores/file/session-policy-store.js +5 -16
  183. package/dist/tools/artifact/artifact-tool.js +2 -0
  184. package/dist/tools/fs/image-admission.d.ts +57 -4
  185. package/dist/tools/fs/image-admission.js +85 -0
  186. package/dist/tools/fs/path-containment.d.ts +2 -0
  187. package/dist/tools/fs/path-containment.js +5 -0
  188. package/dist/tools/fs/shell-read-check.d.ts +31 -6
  189. package/dist/tools/fs/shell-read-check.js +2 -4
  190. package/dist/tools/fs/shell-read-environment.d.ts +10 -25
  191. package/dist/tools/fs/shell-read-environment.js +6 -18
  192. package/dist/tools/fs/shell-read-establish.d.ts +3 -2
  193. package/dist/tools/fs/shell-read-establish.js +5 -5
  194. package/dist/tools/fs/shell-read-judge.d.ts +38 -8
  195. package/dist/tools/fs/shell-read-judge.js +12 -10
  196. package/dist/tools/web.js +2 -2
  197. package/package.json +1 -1
  198. package/test/export-surface.snapshot.json +112 -10
  199. package/wire-changes/core@7.51.0.json +2421 -0
  200. package/wire-changes/core@7.51.1.json +259 -0
@@ -3,7 +3,7 @@ import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermission
3
3
  import { judgeRuleToolName, splitNamespacedName } from "./tool-name-aliases.js";
4
4
  import { catalogToolNames, isShellFaceToolName } from "./tool-registry.js";
5
5
  import { ALLOW_RULE_MEMBERS } from "./allow-rule-shadow.js";
6
- import { COMMAND_RULE_TOOL, adjudicatePersistedDomainRules, adjudicatePersistedNameRules, domainRuleSubjectOf, findAdmittingRule, laneToolNamesOf, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleToolGrammarOf } from "./permission-rule-model.js";
6
+ import { COMMAND_RULE_TOOL, adjudicatePersistedDomainRules, adjudicatePersistedNameRules, commandCoverageOf, domainRuleSubjectOf, laneToolNamesOf, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleToolGrammarOf } from "./permission-rule-model.js";
7
7
  import { pathRuleLaneAnswer } from "./runner/permission-rule-lanes.js";
8
8
  import { callTargetReadingOf } from "./effective-path-target.js";
9
9
  import { splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
@@ -31,20 +31,21 @@ function mountedSpellingOf(name, roster) {
31
31
  }
32
32
  function toolNameReading(name, roster) {
33
33
  const verdict = judgeRuleToolName(name, roster);
34
+ if (verdict.kind === "unprefixed_namespace")
35
+ return { kind: "inert", reason: "tool_name", verdict: verdict.kind };
34
36
  if (verdict.kind !== "current") {
35
- const currentName = verdict.kind === "unprefixed_namespace" ? undefined : verdict.currentName;
36
- return { inert: "tool_name", detail: { verdict: verdict.kind, ...(currentName !== undefined ? { currentName } : {}) } };
37
+ return { kind: "inert", reason: "tool_name", verdict: verdict.kind, guidance: verdict.guidance, ...(verdict.currentName !== undefined ? { currentName: verdict.currentName } : {}) };
37
38
  }
38
39
  if (roster.has(name) || catalogToolNames().has(name) || splitNamespacedName(name) !== undefined)
39
40
  return undefined;
40
41
  const mounted = mountedSpellingOf(name, roster);
41
- return mounted === undefined ? undefined : { inert: "near_miss", detail: { currentName: mounted } };
42
+ return mounted === undefined ? undefined : { kind: "inert", reason: "near_miss", currentName: mounted };
42
43
  }
43
- function readAllowEntry(raw, behavior, roster) {
44
- const inert = (reason) => ({ inert: reason });
45
- const row = (compiled) => ({ compiled });
44
+ export function readRuleEntry(raw, behavior, roster) {
45
+ const inert = (reason) => ({ kind: "inert", reason });
46
+ const row = (compiled) => ({ kind: "row", compiled });
46
47
  if (typeof raw !== "string")
47
- return { inert: "grammar", detail: { valueType: valueTypeLabel(raw) } };
48
+ return { kind: "inert", reason: "grammar", valueType: valueTypeLabel(raw) };
48
49
  if (raw !== raw.trim())
49
50
  return inert("grammar");
50
51
  const parsed = parsePermissionRule(raw);
@@ -82,26 +83,39 @@ function readAllowEntry(raw, behavior, roster) {
82
83
  }
83
84
  return inert(inertReasonOf(read.reject.code));
84
85
  }
86
+ export function compiledEntryName(compiled) {
87
+ switch (compiled.kind) {
88
+ case "tool":
89
+ case "shell":
90
+ return compiled.name;
91
+ case "namespace":
92
+ return compiled.pattern;
93
+ default:
94
+ return compiled.rule.tool;
95
+ }
96
+ }
85
97
  function inertReasonOf(code) {
86
98
  return code === "unsupported.tool" ? "content_unsupported" : "grammar";
87
99
  }
88
- export function compileAllowEntry(raw, behavior, roster) {
89
- const read = readAllowEntry(raw, behavior, roster);
90
- return "compiled" in read ? read.compiled : read.inert;
91
- }
92
100
  export function compileAllowList(entries, behavior, roster) {
93
- const voided = behavior === "allow" && entries.some((e) => compileAllowEntry(e, behavior) === "wildcard_all");
101
+ const voided = behavior === "allow" && entries.some((e) => isWildcardAll(readRuleEntry(e, behavior)));
94
102
  const rows = [];
95
103
  const inert = [];
96
104
  for (const entry of entries) {
97
- const read = typeof entry !== "string" || !voided ? readAllowEntry(entry, behavior, roster) : { inert: "wildcard_all" };
98
- if ("compiled" in read)
105
+ const read = typeof entry !== "string" || !voided ? readRuleEntry(entry, behavior, roster) : { kind: "inert", reason: "wildcard_all" };
106
+ if (read.kind === "row")
99
107
  rows.push({ entry, compiled: read.compiled });
100
- else
101
- inert.push({ entry: typeof entry === "string" ? entry : nonStringMemberLabel(entry), reason: read.inert, ...read.detail });
108
+ else {
109
+ const { kind: _kind, ...why } = read;
110
+ void _kind;
111
+ inert.push({ entry: typeof entry === "string" ? entry : nonStringMemberLabel(entry), ...why });
112
+ }
102
113
  }
103
114
  return { rows, inert };
104
115
  }
116
+ function isWildcardAll(read) {
117
+ return read.kind === "inert" && read.reason === "wildcard_all";
118
+ }
105
119
  function answersTo(req, has) {
106
120
  return heldBy("allowEntry.names", { has }, req);
107
121
  }
@@ -164,7 +178,18 @@ function commandOf(req) {
164
178
  const command = req.args?.command;
165
179
  return tool !== undefined && typeof command === "string" ? { tool, command } : undefined;
166
180
  }
167
- export function judgeAllowEntries(rows, req, behavior, ctx) {
181
+ function admissionCallOf(call, ctx, liveCwd, posture) {
182
+ return { tool: call.tool, command: call.command, cwd: ctx.root, ...(ctx.sessionId !== undefined ? { sessionId: ctx.sessionId } : {}), ...(liveCwd !== undefined ? { execCwd: liveCwd } : {}), posture };
183
+ }
184
+ export function uncoveredCommandSegment(rows, req, ctx, posture) {
185
+ const call = commandOf(req);
186
+ if (call === undefined)
187
+ return undefined;
188
+ const rules = rows.flatMap((r) => (r.behavior === "allow" && r.compiled.kind === "command" ? [r.compiled.rule] : []));
189
+ const coverage = commandCoverageOf(rules, admissionCallOf(call, ctx, ctx.liveCwd(), posture));
190
+ return "uncovered" in coverage ? coverage.uncovered : undefined;
191
+ }
192
+ export function judgeAllowEntries(rows, req, behavior, ctx, posture = "grant") {
168
193
  const live = rows.filter((r) => r.behavior === behavior);
169
194
  if (live.length === 0)
170
195
  return undefined;
@@ -179,8 +204,8 @@ export function judgeAllowEntries(rows, req, behavior, ctx) {
179
204
  if (r.compiled.kind === "command")
180
205
  byRule.set(r.compiled.rule, r);
181
206
  if (behavior === "allow") {
182
- const covering = findAdmittingRule([...byRule.keys()], { tool: call.tool, command: call.command, cwd: ctx.root, ...(ctx.sessionId !== undefined ? { sessionId: ctx.sessionId } : {}), ...(liveCwd !== undefined ? { execCwd: liveCwd } : {}) });
183
- const hits = (covering ?? []).flatMap((rule) => byRule.get(rule) ?? []);
207
+ const coverage = commandCoverageOf([...byRule.keys()], admissionCallOf(call, ctx, liveCwd, posture));
208
+ const hits = ("covering" in coverage ? coverage.covering : []).flatMap((rule) => byRule.get(rule) ?? []);
184
209
  if (hits.length > 0)
185
210
  return hits;
186
211
  }
@@ -319,7 +344,7 @@ export function readAllowRuleShadows(value) {
319
344
  return undefined;
320
345
  }
321
346
  }
322
- const SHADOW_REASON = {
347
+ export const SHADOW_REASON = Object.freeze({
323
348
  content_question: "the call is the engine's own question to a person",
324
349
  hook: "a PreToolUse hook asks about this call — the deployment's own screening is answered by a person",
325
350
  ancestor: "the task that delegated this one owns this question",
@@ -328,8 +353,7 @@ const SHADOW_REASON = {
328
353
  unverifiable_shell_text: "the text this shell will run cannot be read by the engine — it is cleared by confirming this call, never by an allow rule",
329
354
  transcript_write: "this write targets the session transcript — it is cleared by confirming this call, never by an allow rule",
330
355
  org_rule: "an organization policy rule requires approval for this call",
331
- governance_unavailable: "a source of governance for this call cannot currently be read (this deployment's organization policy, or a partition of the person's standing rules), so whether it forbids the call is unknown",
332
- rule_unreadable: "this call could not be read against a deny or ask rule, so whether one forbids it is unknown",
356
+ rule_unreadable: "a rule that may govern this call cannot currently be read (this deployment's organization policy, a partition of the person's standing rules, or this call against a deny or ask rule), so whether one forbids it is unknown",
333
357
  ask_rule: mandateDisclosureOf(undefined),
334
358
  denial_limit_fallback: "the auto-mode classifier refused too many calls in a row, so a person decides",
335
359
  classifier_no_ruling: "the auto-mode classifier could not judge this call, so a person decides",
@@ -346,7 +370,7 @@ const SHADOW_REASON = {
346
370
  mode: "the permission mode puts this question to a person",
347
371
  unstated: "this call arrived already marked as requiring confirmation (an earlier layer of this run, or the task that delegated it, judged it so) — it is cleared by confirming this call, never by a standing allow rule",
348
372
  ordinary: "this call arrived already marked as requiring confirmation (an earlier layer of this run, or the task that delegated it, judged it so) — it is cleared by confirming this call, never by a standing allow rule",
349
- };
373
+ });
350
374
  function memberPhrase(s) {
351
375
  const listed = `${s.entries.join(", ")}${s.omitted !== undefined ? ` and ${s.omitted} more` : ""}`;
352
376
  switch (s.member) {
@@ -1,88 +1,11 @@
1
- import { type RedactionConfidence, type RedactionSource, type ValueFamilyRow } from "./credential-vocabulary.js";
2
- export type { RedactionConfidence, RedactionSource } from "./credential-vocabulary.js";
3
- /** One redaction hit: what was detected, where (ORIGINAL-input coordinates), how confidently, the marker
4
- * literal written in its place, which rule table minted it, and — present when a vocabulary row minted it — the
5
- * value family (the row's stable id; a host-leak hit such as a URL or a path names none). */
6
- export interface RedactionFinding {
7
- kind: string;
8
- span: readonly [number, number];
9
- confidence: RedactionConfidence;
10
- marker: string;
11
- source: RedactionSource;
12
- family?: string;
13
- }
14
- /** The minimum a finding must declare to be summarizable. Both text findings ({@link RedactionFinding})
15
- * and the env arm's entry findings satisfy it, so {@link summarizeRedactions} is ONE helper rather than
16
- * one per material kind. */
17
- export interface SummarizableFinding {
18
- kind: string;
19
- confidence: RedactionConfidence;
20
- }
21
- /** ANG-3 (envelope declaration) — render findings as ONE human-readable line, e.g.
22
- * `"3 redactions: prefixed-token (high) x1, url (medium) x1, fs-path (low) x1"`, so an upper layer can
23
- * declare to its consumer how much was removed and how confidently.
24
- *
25
- * PURE, and deliberately only that: it reads the array, returns a string, and injects nothing anywhere.
26
- * Attaching this line to an output is the caller's decision — a redaction pipeline that silently
27
- * appended prose to the very text it was asked to bound would break both the size contract
28
- * (`boundedString`) and the byte-invariance the whole channel is built on.
29
- *
30
- * Grouping is by kind AND confidence together: collapsing on kind alone would report whichever
31
- * confidence happened to arrive first for the rest of that group, which is exactly the silent
32
- * mis-statement ANG-4 exists to prevent. */
33
- export declare function summarizeRedactions(findings: readonly SummarizableFinding[]): string;
34
- /** The optional collector every redaction entry point accepts. `preexistingMarkers` (ANG-11) counts
35
- * marker-shaped literals ALREADY present in the input before any pass ran — a nonzero value means the
36
- * text arrived pre-redacted or carries caller-declared markers, either way "marker present" is not proof this
37
- * pipeline ran. Caller-owned: pass `{ findings: [] }` and read it back after the call. */
38
- export interface RedactionReport {
39
- findings: RedactionFinding[];
40
- preexistingMarkers?: number;
41
- }
42
- /** A declared redaction pass — the unit {@link runRedactionPasses} executes and reports on. */
43
- export interface RedactionPass {
44
- kind: string;
45
- confidence: RedactionConfidence;
46
- marker: string;
47
- /** The rule table that owns this pass's rule — copied verbatim onto every finding it mints (ANG-11), whoever
48
- * runs the pass. */
49
- source: RedactionSource;
50
- re: RegExp;
51
- /** A literal replacement (only `$1` substitution is used by these passes) or a replacer fn. */
52
- replace: string | ((match: string, ...groups: string[]) => string);
53
- /** The value family a hit belongs to: one id, or — for a pass joining several rows — the id of the row that
54
- * matches at the hit's offset in the pass input. Absent = the pass is no vocabulary row. */
55
- family?: string | ((input: string, at: number) => string);
56
- }
57
- /**
58
- * Run the declared passes in order, collecting findings (original-input spans) when a report is given.
59
- *
60
- * FORMAT CHARACTERS (#532): the passes scan a VIEW of the input with every `\p{Cf}` removed, so a
61
- * credential split by a zero-width character is one token to the scanner, not two fragments of which
62
- * only the first (or neither) matches. The OUTPUT keeps every format character that sits OUTSIDE a
63
- * replaced span — a joiner in an emoji sequence, a non-joiner in Persian text, a soft hyphen in a word
64
- * — and drops only those INSIDE the span the marker replaces (they were part of the secret). A
65
- * format-free input takes the exact historical path (byte-identical output). The view's replacements and
66
- * every boundary scan's are taken as disjoint spans of their input (a later pass that matched across an
67
- * earlier pass's inserted text is rendered from the removals instead), so the spans always map back
68
- * exactly and no scan is skipped.
69
- */
70
- export declare function runRedactionPasses(input: string, passes: readonly RedactionPass[], report?: RedactionReport): string;
71
- /**
72
- * The egress view rendered as passes, in the vocabulary's order: one pass per group (a group of several rows is ONE
73
- * leftmost alternation, its hits attributed to the row that matches at the hit's offset). A row whose wide form
74
- * captures a label keeps it ahead of the marker; `replacers` renders a kind whose marker placement is a policy (the
75
- * URL userinfo).
76
- */
77
- export declare function egressPasses(groups: ReadonlyArray<readonly ValueFamilyRow[]>, replacers?: Readonly<Record<string, (match: string, ...groups: string[]) => string>>): RedactionPass[];
78
- export declare function scrubSecrets(s: string, report?: RedactionReport): string;
79
- /** A SHORT, SECRET-SCRUBBED primary-arg summary. Abbreviation REDUCES the surface; a final {@link scrubSecrets}
80
- * pass is the leak boundary (the value is PERSISTED + SSE-forwarded and may be read by a platform operator in a
81
- * multi-tenant deployment). Rules:
1
+ import { type Egress } from "./untrusted-egress.js";
2
+ /** A SHORT, SECRET-SCRUBBED primary-arg summary. Abbreviation REDUCES the surface; the `same-principal` tier's pass
3
+ * is the leak boundary, on whichever field the summary came from (the value is PERSISTED + SSE-forwarded and may be
4
+ * read by a platform operator in a multi-tenant deployment). Rules:
82
5
  * • command → the command NAME (skip leading `VAR=val` env-prefixes; bail to `undefined` on a quoted assignment
83
6
  * or a lone assignment; basename a path-form command; `+ …` if it had args).
84
7
  * • file path → BASENAME only (no host directory layout).
85
- * • url → `new URL` origin+pathname (strips `user:pass@` userinfo + query/fragment).
86
- * • else → pattern/query/description, else the first NON-sensitively-named string field.
87
- * Then scrub-secrets + code-point-truncate. */
88
- export declare function primaryActivityArg(args: unknown): string | undefined;
8
+ * • url → `new URL` origin+pathname (drops `user:pass@` userinfo + query/fragment).
9
+ * • else → pattern/query/description, else the first string field whose name is no credential name.
10
+ * Then the tier's pass, then a code-point cut. */
11
+ export declare function primaryActivityArg(args: unknown): Egress<"same-principal"> | undefined;
@@ -1,389 +1,7 @@
1
1
  import { writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
2
- import { CREDENTIAL_VOCABULARY, egressPassGroups } from "./credential-vocabulary.js";
2
+ import { isSensitiveRecordKey } from "./credential-vocabulary.js";
3
+ import { egressText } from "./untrusted-egress.js";
3
4
  const ACTIVITY_ARG_MAX = 80;
4
- const SENSITIVE_KEY = /token|secret|key|password|passwd|credential|auth/i;
5
- export function summarizeRedactions(findings) {
6
- if (findings.length === 0)
7
- return "no redactions";
8
- const counts = new Map();
9
- for (const f of findings) {
10
- const label = `${f.kind} (${f.confidence})`;
11
- counts.set(label, (counts.get(label) ?? 0) + 1);
12
- }
13
- const parts = [...counts].map(([label, n]) => `${label} x${n}`);
14
- return `${findings.length} redaction${findings.length === 1 ? "" : "s"}: ${parts.join(", ")}`;
15
- }
16
- function passZeroMapper(batches) {
17
- const cursors = batches.map(() => ({ i: 0, delta: 0 }));
18
- return (pos) => {
19
- let p = pos;
20
- for (let b = batches.length - 1; b >= 0; b--) {
21
- const edits = batches[b];
22
- const c = cursors[b];
23
- let mapped;
24
- while (mapped === undefined && c.i < edits.length) {
25
- const e = edits[c.i];
26
- const outStart = e.at + c.delta;
27
- if (p < outStart)
28
- mapped = p - c.delta;
29
- else if (p < outStart + e.insertedLen)
30
- mapped = e.at;
31
- else {
32
- c.delta += e.insertedLen - e.removedLen;
33
- c.i++;
34
- }
35
- }
36
- p = mapped ?? p - c.delta;
37
- }
38
- return p;
39
- };
40
- }
41
- const PREEXISTING_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g;
42
- const FORMAT_CHAR_RE = /\p{Cf}/u;
43
- const MAX_FORMAT_SLOT_SCANS = 64;
44
- const REDACTED_WHOLE = "[redacted]";
45
- function finding(pass, span, family) {
46
- const f = { kind: pass.kind, confidence: pass.confidence, span, marker: pass.marker, source: pass.source };
47
- if (family !== undefined)
48
- f.family = family;
49
- return f;
50
- }
51
- function insertedFor(pass, match, groups) {
52
- return typeof pass.replace === "string"
53
- ? pass.replace.replace(/\$(\d)/g, (_m, d) => groups[Number(d) - 1] ?? "")
54
- : pass.replace(match, ...groups);
55
- }
56
- function removedRegion(pass, match, groups, inserted) {
57
- if (typeof pass.replace === "string") {
58
- const parts = pass.replace.split(/(\$\d)/).filter((p) => p !== "");
59
- const isRef = (p) => /^\$\d$/.test(p);
60
- const refText = (p) => groups[Number(p[1]) - 1] ?? "";
61
- let head = "";
62
- for (const p of parts) {
63
- if (!isRef(p))
64
- break;
65
- head += refText(p);
66
- }
67
- let tail = "";
68
- for (let i = parts.length - 1; i >= 0 && isRef(parts[i]); i--)
69
- tail = refText(parts[i]) + tail;
70
- if (parts.every(isRef))
71
- return [0, match.length];
72
- if (!match.startsWith(head))
73
- head = "";
74
- if (!match.endsWith(tail) || head.length + tail.length > match.length)
75
- tail = "";
76
- return [head.length, match.length - tail.length];
77
- }
78
- const markerAt = pass.marker === "" ? -1 : inserted.lastIndexOf(pass.marker);
79
- const headStop = markerAt < 0 ? inserted.length : markerAt;
80
- const tailStop = markerAt < 0 ? inserted.length : inserted.length - markerAt - pass.marker.length;
81
- let head = 0;
82
- while (head < match.length && head < headStop && match[head] === inserted[head])
83
- head++;
84
- let tail = 0;
85
- while (tail < match.length - head && tail < inserted.length - head && tail < tailStop && match[match.length - 1 - tail] === inserted[inserted.length - 1 - tail])
86
- tail++;
87
- return [head, match.length - tail];
88
- }
89
- const SOLO_SCAN_WORK_PER_CHAR = 8;
90
- const SOLO_SCAN_WORK_FLOOR = 1 << 17;
91
- function soloHits(input, passes) {
92
- const hits = [];
93
- passes.forEach((pass, i) => {
94
- const re = new RegExp(pass.re.source, pass.re.flags.includes("g") ? pass.re.flags : `${pass.re.flags}g`);
95
- const rescans = pass.family !== undefined;
96
- let work = Math.max(SOLO_SCAN_WORK_PER_CHAR * input.length, SOLO_SCAN_WORK_FLOOR);
97
- for (let m = re.exec(input); m !== null; m = re.exec(input)) {
98
- const at = m.index;
99
- const familyAt = () => (typeof pass.family === "function" ? pass.family(input, at) : pass.family);
100
- if (rescans) {
101
- work -= m[0].length;
102
- if (work < 0) {
103
- hits.push({ start: at, end: input.length, marker: pass.marker === "" ? REDACTED_WHOLE : pass.marker, pass: i, family: familyAt(), matchStart: at, matchEnd: input.length });
104
- break;
105
- }
106
- re.lastIndex = at + 1;
107
- }
108
- else
109
- re.lastIndex = at + Math.max(m[0].length, 1);
110
- const inserted = insertedFor(pass, m[0], m.slice(1));
111
- if (inserted === m[0])
112
- continue;
113
- const [a, b] = removedRegion(pass, m[0], m.slice(1), inserted);
114
- if (b <= a)
115
- continue;
116
- const family = familyAt();
117
- hits.push({ start: at + a, end: at + b, marker: inserted.slice(a, inserted.length - (m[0].length - b)), pass: i, family, matchStart: at, matchEnd: at + m[0].length });
118
- }
119
- });
120
- return hits;
121
- }
122
- function unremovedHits(hits, removals) {
123
- const gone = [];
124
- for (const r of [...removals].filter((x) => x.end > x.start).sort((x, y) => x.start - y.start || y.end - x.end)) {
125
- const last = gone[gone.length - 1];
126
- if (last !== undefined && r.start <= last[1])
127
- last[1] = Math.max(last[1], r.end);
128
- else
129
- gone.push([r.start, r.end]);
130
- }
131
- return hits.filter((h) => {
132
- let lo = 0;
133
- let hi = gone.length - 1;
134
- let at = -1;
135
- while (lo <= hi) {
136
- const mid = (lo + hi) >> 1;
137
- if (gone[mid][0] <= h.start) {
138
- at = mid;
139
- lo = mid + 1;
140
- }
141
- else
142
- hi = mid - 1;
143
- }
144
- return at === -1 || gone[at][1] < h.end;
145
- });
146
- }
147
- function sequentialRemovals(batches) {
148
- const removals = [];
149
- for (let j = 0; j < batches.length; j++) {
150
- const toPassZero = passZeroMapper(batches.slice(0, j));
151
- for (const e of batches[j]) {
152
- const start = toPassZero(e.at + e.head);
153
- const end = toPassZero(e.at + e.removedLen - e.tail);
154
- removals.push({ start, end: Math.max(start, end), marker: e.inserted.slice(e.head, e.insertedLen - e.tail), pass: e.pass, family: e.family });
155
- }
156
- }
157
- return removals;
158
- }
159
- function renderRemovals(input, removals) {
160
- const sorted = removals.filter((r) => r.end > r.start || r.marker !== "").sort((x, y) => x.start - y.start || y.end - x.end);
161
- const edits = [];
162
- let out = "";
163
- let cursor = 0;
164
- for (let i = 0; i < sorted.length;) {
165
- const start = sorted[i].start;
166
- let end = sorted[i].end;
167
- let widest;
168
- for (; i < sorted.length && (sorted[i].start < end || sorted[i].start === start || (sorted[i].marker === "" && sorted[i].start === end)); i++) {
169
- const r = sorted[i];
170
- if (r.end > end)
171
- end = r.end;
172
- if (r.marker !== "" && (widest === undefined || r.end - r.start > widest.end - widest.start))
173
- widest = r;
174
- }
175
- const marker = widest?.marker ?? REDACTED_WHOLE;
176
- out += input.slice(cursor, start) + marker;
177
- cursor = end;
178
- edits.push({ at: start, removedLen: end - start, insertedLen: marker.length, inserted: marker, family: widest?.family, head: 0, tail: 0, pass: widest?.pass ?? sorted[i - 1].pass });
179
- }
180
- return { out: out + input.slice(cursor), edits };
181
- }
182
- function runPasses(input, passes, findings, toOriginal) {
183
- const batches = [];
184
- let cur = input;
185
- passes.forEach((pass, passIndex) => {
186
- const edits = [];
187
- const passInput = cur;
188
- const toPassZero = passZeroMapper(batches.slice());
189
- cur = cur.replace(pass.re, (...args) => {
190
- const match = args[0];
191
- const offset = args[args.length - 2];
192
- const groups = args.slice(1, -2);
193
- const inserted = insertedFor(pass, match, groups);
194
- if (inserted === match)
195
- return inserted;
196
- const family = typeof pass.family === "function" ? pass.family(passInput, offset) : pass.family;
197
- if (findings !== undefined)
198
- findings.push({ pass: passIndex, f: finding(pass, [toOriginal(toPassZero(offset)), toOriginal(toPassZero(offset + match.length))], family) });
199
- const [a, b] = removedRegion(pass, match, groups, inserted);
200
- edits.push({ at: offset, removedLen: match.length, insertedLen: inserted.length, inserted, family, head: a, tail: match.length - b, pass: passIndex });
201
- return inserted;
202
- });
203
- batches.push(edits);
204
- });
205
- return { out: cur, batches };
206
- }
207
- function applyPasses(input, passes, report, toOriginal, disjoint = false) {
208
- const found = report === undefined ? undefined : [];
209
- const plain = runPasses(input, passes, found, toOriginal);
210
- const solo = soloHits(input, passes);
211
- const sequential = solo.length === 0 ? [] : sequentialRemovals(plain.batches);
212
- const outer = unremovedHits(solo, sequential);
213
- const mappable = !disjoint || replacedSpans(plain.batches) !== undefined;
214
- const whole = outer.length === 0 && mappable ? undefined : renderRemovals(input, [...sequential, ...outer]);
215
- if (whole === undefined || (whole.out === plain.out && mappable)) {
216
- if (report !== undefined)
217
- report.findings.push(...found.map((x) => x.f));
218
- return plain;
219
- }
220
- if (report !== undefined) {
221
- const seen = new Set(found.map((x) => `${x.pass}:${x.f.span[0]}:${x.f.span[1]}`));
222
- for (const h of outer) {
223
- const span = [toOriginal(h.matchStart), toOriginal(h.matchEnd)];
224
- const key = `${h.pass}:${span[0]}:${span[1]}`;
225
- if (seen.has(key))
226
- continue;
227
- seen.add(key);
228
- found.push({ pass: h.pass, f: finding(passes[h.pass], span, h.family) });
229
- }
230
- found.sort((x, y) => x.pass - y.pass || x.f.span[0] - y.f.span[0]);
231
- report.findings.push(...found.map((x) => x.f));
232
- }
233
- return { out: whole.out, batches: [whole.edits] };
234
- }
235
- function replacedSpans(batches) {
236
- const spans = [];
237
- for (let j = 0; j < batches.length; j++) {
238
- const toPassZero = passZeroMapper(batches.slice(0, j));
239
- for (const e of batches[j]) {
240
- const s0 = toPassZero(e.at);
241
- const e0 = toPassZero(e.at + e.removedLen);
242
- if (e0 - s0 !== e.removedLen)
243
- return undefined;
244
- spans.push({ start: s0, end: e0, inserted: e.inserted, pass: e.pass, family: e.family });
245
- }
246
- }
247
- spans.sort((a, b) => a.start - b.start);
248
- for (let i = 1; i < spans.length; i++)
249
- if (spans[i].start < spans[i - 1].end)
250
- return undefined;
251
- return spans;
252
- }
253
- export function runRedactionPasses(input, passes, report) {
254
- if (report !== undefined && report.preexistingMarkers === undefined) {
255
- report.preexistingMarkers = input.match(PREEXISTING_MARKER_RE)?.length ?? 0;
256
- }
257
- if (!FORMAT_CHAR_RE.test(input))
258
- return applyPasses(input, passes, report, (pos) => pos).out;
259
- const viewChars = [];
260
- const viewToOriginal = [];
261
- const formatAtSlot = [];
262
- const charStartOffsets = [];
263
- {
264
- let orig = 0;
265
- let viewOff = 0;
266
- let slotBuf = "";
267
- for (const ch of input) {
268
- if (FORMAT_CHAR_RE.test(ch)) {
269
- slotBuf += ch;
270
- }
271
- else {
272
- formatAtSlot.push(slotBuf);
273
- slotBuf = "";
274
- viewToOriginal.push(orig);
275
- charStartOffsets.push(viewOff);
276
- viewChars.push(ch);
277
- viewOff += ch.length;
278
- }
279
- orig += ch.length;
280
- }
281
- formatAtSlot.push(slotBuf);
282
- viewToOriginal.push(input.length);
283
- charStartOffsets.push(viewOff);
284
- }
285
- const view = viewChars.join("");
286
- const offsetToCharIndex = new Map();
287
- charStartOffsets.forEach((off, idx) => offsetToCharIndex.set(off, idx));
288
- const toOriginal = (pos) => {
289
- const idx = offsetToCharIndex.get(pos);
290
- return idx === undefined ? input.length : viewToOriginal[idx];
291
- };
292
- const { batches } = applyPasses(view, passes, report, toOriginal, true);
293
- const viewSpans = replacedSpans(batches);
294
- if (viewSpans === undefined)
295
- return REDACTED_WHOLE;
296
- const spans = viewSpans.map((sp) => {
297
- const startIdx = offsetToCharIndex.get(sp.start) ?? viewChars.length;
298
- const endIdx = offsetToCharIndex.get(sp.end) ?? viewChars.length;
299
- const lastIdx = endIdx - 1;
300
- const endOrig = lastIdx >= startIdx && lastIdx < viewChars.length ? viewToOriginal[lastIdx] + viewChars[lastIdx].length : viewToOriginal[startIdx];
301
- return { start: viewToOriginal[startIdx], end: endOrig, inserted: sp.inserted };
302
- });
303
- const viewOffsetToOriginal = (viewStart, viewEnd) => {
304
- const startIdx = offsetToCharIndex.get(viewStart) ?? viewChars.length;
305
- const endIdx = offsetToCharIndex.get(viewEnd) ?? viewChars.length;
306
- const lastIdx = endIdx - 1;
307
- const endOrig = lastIdx >= startIdx && lastIdx < viewChars.length ? viewToOriginal[lastIdx] + viewChars[lastIdx].length : viewToOriginal[startIdx];
308
- return { start: viewToOriginal[startIdx], end: endOrig };
309
- };
310
- const cutOffsets = [];
311
- for (let k = 1; k < viewChars.length; k++)
312
- if (formatAtSlot[k] !== "")
313
- cutOffsets.push(charStartOffsets[k]);
314
- if (cutOffsets.length > MAX_FORMAT_SLOT_SCANS)
315
- return REDACTED_WHOLE;
316
- const extra = [];
317
- const rawSpans = replacedSpans(applyPasses(input, passes, undefined, (pos) => pos, true).batches);
318
- if (rawSpans !== undefined)
319
- extra.push(...rawSpans);
320
- for (const cut of cutOffsets) {
321
- const cutSpans = replacedSpans(applyPasses(view.slice(cut), passes, undefined, (pos) => pos, true).batches);
322
- if (cutSpans === undefined)
323
- continue;
324
- for (const c of cutSpans) {
325
- const o = viewOffsetToOriginal(c.start + cut, c.end + cut);
326
- extra.push({ ...c, start: o.start, end: o.end });
327
- }
328
- }
329
- extra.sort((a, b) => a.start - b.start);
330
- const firstEndingAfter = (pos) => {
331
- let lo = 0;
332
- let hi = spans.length;
333
- while (lo < hi) {
334
- const mid = (lo + hi) >> 1;
335
- if (spans[mid].end > pos)
336
- hi = mid;
337
- else
338
- lo = mid + 1;
339
- }
340
- return lo;
341
- };
342
- for (const r of extra) {
343
- const at = firstEndingAfter(r.start);
344
- const hit = spans[at];
345
- if (hit !== undefined && hit.start < r.end) {
346
- if (r.start < hit.start)
347
- hit.start = r.start;
348
- if (r.end > hit.end)
349
- hit.end = r.end;
350
- let next = at + 1;
351
- while (next < spans.length && spans[next].start < hit.end)
352
- hit.end = Math.max(hit.end, spans[next++].end);
353
- if (next > at + 1)
354
- spans.splice(at + 1, next - at - 1);
355
- continue;
356
- }
357
- spans.splice(at, 0, { start: r.start, end: r.end, inserted: r.inserted });
358
- if (report !== undefined)
359
- report.findings.push(finding(passes[r.pass], [r.start, r.end], r.family));
360
- }
361
- let result = "";
362
- let cursor = 0;
363
- for (const sp of spans) {
364
- result += input.slice(cursor, sp.start) + sp.inserted;
365
- cursor = sp.end;
366
- }
367
- result += input.slice(cursor);
368
- return result;
369
- }
370
- const BRACKET_MARKER = { "url-credentials": "[redacted-credentials]" };
371
- const bracketMarker = (kind) => BRACKET_MARKER[kind] ?? "[redacted]";
372
- const capturesLabel = (re) => new RegExp(`${re.source}|`).exec("").length > 1;
373
- export function egressPasses(groups, replacers = {}) {
374
- return groups.map((rows) => {
375
- const head = rows[0];
376
- const marker = bracketMarker(head.kind);
377
- const re = rows.length === 1 ? head.match : new RegExp(rows.map((r) => `(?:${r.match.source})`).join("|"), head.match.flags);
378
- const sticky = rows.map((r) => ({ id: r.id, re: new RegExp(r.match.source, r.match.flags.replace("g", "") + "y") }));
379
- const family = rows.length === 1 ? head.id : (input, at) => sticky.find((s) => ((s.re.lastIndex = at), s.re.test(input)))?.id ?? head.id;
380
- return { kind: head.kind, confidence: head.confidence, marker, source: head.source, re, replace: replacers[head.kind] ?? (capturesLabel(head.match) ? `$1${marker}` : marker), family };
381
- });
382
- }
383
- const SHAPE_PASSES = egressPasses(egressPassGroups(CREDENTIAL_VOCABULARY).filter((rows) => rows[0].source === "arg-summary"));
384
- export function scrubSecrets(s, report) {
385
- return runRedactionPasses(s, SHAPE_PASSES, report);
386
- }
387
5
  function truncCodePoints(s) {
388
6
  const cp = Array.from(s);
389
7
  return cp.length > ACTIVITY_ARG_MAX ? cp.slice(0, ACTIVITY_ARG_MAX).join("") + "…" : s;
@@ -437,7 +55,7 @@ export function primaryActivityArg(args) {
437
55
  val = str("pattern") ?? str("query") ?? str("description");
438
56
  if (val === undefined) {
439
57
  for (const k of Object.keys(a)) {
440
- if (SENSITIVE_KEY.test(k))
58
+ if (isSensitiveRecordKey(k))
441
59
  continue;
442
60
  const v = a[k];
443
61
  if (typeof v === "string" && v.length > 0) {
@@ -449,5 +67,5 @@ export function primaryActivityArg(args) {
449
67
  }
450
68
  if (val === undefined)
451
69
  return undefined;
452
- return truncCodePoints(scrubSecrets(val));
70
+ return truncCodePoints(egressText("same-principal", val));
453
71
  }