@piagent/platform 1.5.5 → 1.6.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.
Files changed (227) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +9 -8
  3. package/README.vi.md +9 -5
  4. package/adapters/web-frontend/profile.json +2 -4
  5. package/architecture/layers.json +18 -3
  6. package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
  7. package/benchmarks/deep-logic-v1/grade.mjs +272 -10
  8. package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
  9. package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
  10. package/benchmarks/deep-logic-v1/project/package.json +1 -1
  11. package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
  12. package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
  13. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
  14. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
  15. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
  16. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
  17. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
  18. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
  19. package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
  20. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
  21. package/benchmarks/deep-logic-v1/suite.json +51 -3
  22. package/benchmarks/deep-logic-v1/variant.mjs +22 -1
  23. package/benchmarks/production-v1/spend-control.v1.json +68 -0
  24. package/benchmarks/production-v1/suite.json +42 -3
  25. package/catalog/capabilities.json +6 -6
  26. package/docs/command-reference-vietnamese.md +6 -6
  27. package/docs/context-engine.md +7 -1
  28. package/docs/distribution-standard.md +7 -7
  29. package/docs/en/architecture.md +1 -0
  30. package/docs/languages.json +4 -4
  31. package/docs/model-options.md +8 -5
  32. package/docs/operator-manual-vietnamese.md +13 -11
  33. package/docs/project-adapters.md +2 -2
  34. package/docs/publishing-for-teams.md +5 -0
  35. package/docs/quality-benchmark.md +271 -62
  36. package/docs/release-install-policy.md +10 -9
  37. package/docs/runtime-policy-design.md +4 -4
  38. package/docs/runtime-quality-baseline.md +13 -6
  39. package/docs/security-threat-model.md +2 -2
  40. package/docs/task-lifecycle-vietnamese.md +2 -2
  41. package/docs/team-onboarding.md +2 -2
  42. package/docs/usage-observability.md +7 -2
  43. package/docs/vi/architecture.md +1 -0
  44. package/docs-site/architecture.html +1 -1
  45. package/docs-site/benchmark.html +1 -1
  46. package/docs-site/commands.html +1 -1
  47. package/docs-site/context-engine.html +1 -1
  48. package/docs-site/en/architecture.html +1 -1
  49. package/docs-site/en/benchmark.html +1 -1
  50. package/docs-site/en/commands.html +1 -1
  51. package/docs-site/en/context-engine.html +1 -1
  52. package/docs-site/en/index.html +1 -1
  53. package/docs-site/en/mcp-approval.html +1 -1
  54. package/docs-site/en/mcp-auth.html +1 -1
  55. package/docs-site/en/mcp-servers.html +1 -1
  56. package/docs-site/en/mcp.html +1 -1
  57. package/docs-site/en/permissions.html +1 -1
  58. package/docs-site/en/profiles.html +1 -1
  59. package/docs-site/en/quickstart.html +1 -1
  60. package/docs-site/en/runtime.html +1 -1
  61. package/docs-site/en/scope.html +1 -1
  62. package/docs-site/en/security.html +1 -1
  63. package/docs-site/en/team.html +1 -1
  64. package/docs-site/en/whats-new.html +1 -1
  65. package/docs-site/en/workflows.html +1 -1
  66. package/docs-site/index.html +1 -1
  67. package/docs-site/mcp-approval.html +1 -1
  68. package/docs-site/mcp-auth.html +1 -1
  69. package/docs-site/mcp-servers.html +1 -1
  70. package/docs-site/mcp.html +1 -1
  71. package/docs-site/permissions.html +1 -1
  72. package/docs-site/profiles.html +1 -1
  73. package/docs-site/quickstart.html +1 -1
  74. package/docs-site/runtime.html +1 -1
  75. package/docs-site/scope.html +1 -1
  76. package/docs-site/security.html +1 -1
  77. package/docs-site/team.html +1 -1
  78. package/docs-site/whats-new.html +1 -1
  79. package/docs-site/workflows.html +1 -1
  80. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  81. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  82. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  83. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  84. package/evals/fs4-readiness-matrix.v1.json +2 -2
  85. package/evals/fs5-causal-arm.v1.json +2 -2
  86. package/evals/fs5-pilot-protocol.v1.json +2 -2
  87. package/evals/fs5-pilot-protocol.v4.json +6 -5
  88. package/evals/fs5-pilot-protocol.v5.json +7 -6
  89. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  90. package/package.json +3 -1
  91. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  92. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  93. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  94. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  95. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  96. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  97. package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
  98. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  99. package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
  100. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  101. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  102. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  103. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  104. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  105. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  106. package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
  107. package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
  108. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  109. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  110. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
  111. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  112. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  113. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  114. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  115. package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
  116. package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
  117. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  118. package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
  119. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  120. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  121. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  122. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  123. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  124. package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
  125. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  126. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  127. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  128. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  129. package/packages/piagent-core/extensions/context-engine.js +148 -145
  130. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  131. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  132. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  133. package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
  134. package/packages/piagent-core/extensions/criterion-graph.js +78 -8
  135. package/packages/piagent-core/extensions/guard-types.ts +2 -0
  136. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  137. package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
  138. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  139. package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
  140. package/packages/piagent-core/extensions/task-state.js +40 -134
  141. package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
  142. package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
  143. package/packages/piagent-core/package.json +1 -1
  144. package/packages/piagent-core/policies/base-policy.json +2 -0
  145. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  146. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  147. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  148. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  149. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  150. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  151. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  152. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  153. package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
  154. package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
  155. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  156. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
  157. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  158. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
  159. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
  160. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  161. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  162. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  163. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
  164. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
  165. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  166. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  167. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  168. package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
  169. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  170. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
  171. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
  172. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  173. package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
  174. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  175. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  176. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
  177. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  178. package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
  179. package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
  180. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
  181. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
  182. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  183. package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
  184. package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
  185. package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
  186. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  187. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  188. package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
  189. package/packages/piagent-core/security/sensitive-data.js +13 -218
  190. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  191. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  192. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  193. package/packages/piagent-core/security/sensitive-text.js +234 -0
  194. package/packages/piagent-core/subagents/piagent-worker.md +1 -1
  195. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
  196. package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
  197. package/packages/piagent-webui/dist/client/index.html +1 -1
  198. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  199. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  200. package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
  201. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  202. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  203. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  204. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  205. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
  206. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  207. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
  208. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  209. package/packages/piagent-webui/server/transcript-projection.ts +22 -4
  210. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  211. package/schemas/piagent-webui/catalog-v1.json +9 -0
  212. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  213. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  214. package/schemas/task-contract.schema.json +12 -1
  215. package/scripts/benchmark-runner-core.mjs +355 -359
  216. package/scripts/benchmark-runner-finalization.mjs +309 -0
  217. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  218. package/scripts/benchmark-runner-support.mjs +200 -0
  219. package/scripts/benchmark-session.mjs +95 -41
  220. package/scripts/configure-model-scope.sh +4 -3
  221. package/scripts/helper-evaluation.mjs +1 -1
  222. package/scripts/install-global.sh +2 -2
  223. package/scripts/pi-usage-history.mjs +37 -10
  224. package/scripts/setup.sh +2 -2
  225. package/scripts/verify-local.sh +5 -1
  226. package/templates/global/settings.json +2 -1
  227. package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { changedFileAcceptanceCorpus, emptyAcceptanceCorpus, isAcceptanceTestPath } from "./acceptance-language-adapters.js";
4
+ import { durableContextEvidenceEntries, hasDurableContextEvidence } from "./context-evidence.js";
4
5
  import { matchesAnyPath, normalizePathCandidate } from "./policy-core.js";
5
6
  import { baselineReturnRepresentationConflicts, returnRepresentationGuidance } from "./return-contract.js";
6
7
  import { hasLengthPrefixedIdentityKey, tenantAssertionSignals } from "./tenant-contract.js";
@@ -103,23 +104,46 @@ function changedFilesRespectBoundaries(changedFiles, boundaryPatterns) {
103
104
 
104
105
  export function inferAcceptanceObligations(text, changeMode = "source-change") {
105
106
  const value = normalizedText(text);
107
+ const accessControlText = value
108
+ .replace(/`[^`\n]*`/g, " ")
109
+ .replace(/\b(?:[a-z0-9_.@-]+\/)+[a-z0-9_.@/-]+\b/g, " ");
106
110
  const obligations = [];
107
111
  if (changeMode === "read-only" || READ_ONLY_BOUNDARY.test(value)) {
108
112
  obligations.push("read-only-evidence");
109
113
  }
110
- const explicitAuthorization = includesAny(value, [
111
- /\bauth(?:orization)?\b/, /\bunauthoriz(?:ed|ation)\b/, /\bpermission\b/, /\brole\b/, /\badmin\b/, /\bdeny\b/, /\bdenied\b/
114
+ const actorAccessControl = includesAny(accessControlText, [
115
+ /\b(?:admins?|roles?|owners?|users?|callers?|resources?)\b[\s\S]{0,80}\b(?:access|allow(?:ed)?|deny|denied|block(?:ed)?|forbid(?:den)?|manag(?:e|es|ed|ing))\b/,
116
+ /\b(?:access|allow(?:ed)?|deny|denied|block(?:ed)?|forbid(?:den)?|manag(?:e|es|ed|ing))\b[\s\S]{0,80}\b(?:admins?|roles?|owners?|users?|callers?|resources?)\b/
112
117
  ]);
113
- const ownerAccessControl = /\bowner\b/.test(value) && includesAny(value, [
118
+ const explicitAuthorization = includesAny(accessControlText, [
119
+ /\bauth(?:orization)?\b/, /\bunauthoriz(?:ed|ation)\b/, /\bpermission\b/
120
+ ]) || actorAccessControl;
121
+ const ownerAccessControl = /\bowner\b/.test(accessControlText) && includesAny(accessControlText, [
114
122
  /\busers?\b/, /\bresources?\b/, /\baccess\b/, /\bmanag(?:e|es|ed|ing)\b/, /\bauth(?:orization)?\b/, /\bpermission\b/, /\broles?\b/
115
123
  ]);
116
124
  if (explicitAuthorization || ownerAccessControl) {
117
125
  obligations.push("authorization-deny-case");
118
126
  }
119
- if (includesAny(value, [/\btenant\b/, /\bcross-tenant\b/, /\bsame tenant\b/, /\btenantid\b/])) {
120
- if (includesAny(value, [/\bcache\b/, /\bcache[- ]?key\b/, /\bstorage\b/, /\bcollision\b/, /\bentity\b/, /\bsame tuple\b/])) {
127
+ const tenantMentioned = includesAny(value, [/\btenants?\b/, /\bcross[- ]tenant\b/, /\bsame[- ]tenant\b/, /\btenantid\b/]);
128
+ const tenantStorage = tenantMentioned && includesAny(value, [
129
+ /\bcache\b/, /\bcache[- ]?key\b/, /\bstorage\b/, /\bcollision\b/, /\bentity\b/, /\bsame tuple\b/
130
+ ]);
131
+ const strongAuthorization = includesAny(accessControlText, [
132
+ /\bauth(?:orization)?\b/, /\bunauthoriz(?:ed|ation)\b/, /\bpermission\b/
133
+ ]);
134
+ const tenantActorAccess = tenantMentioned && actorAccessControl;
135
+ const explicitTenantAccessBoundary = includesAny(value, [
136
+ /\bcross[- ]tenant\b/, /\bsame[- ]tenant\b/, /\btenant boundary\b/,
137
+ /\btenantid\b[\s\S]{0,100}\b(?:equal|match|same non-empty|access|allow|deny|block|forbid)/,
138
+ /\b(?:access|allow|deny|block|forbid)[\s\S]{0,100}\btenantid\b/
139
+ ]) && includesAny(value, [
140
+ /\baccess\b/, /\ballow(?:ed)?\b/, /\b(?:deny|denied)\b/, /\bblock(?:ed)?\b/, /\bforbid(?:den)?\b/,
141
+ /\bequal\b/, /\bmatch(?:es|ed|ing)?\b/, /\bsame non-empty\b/
142
+ ]);
143
+ if (tenantMentioned) {
144
+ if (tenantStorage) {
121
145
  obligations.push("tenant-storage-isolation");
122
- } else {
146
+ } else if (strongAuthorization || ownerAccessControl || tenantActorAccess || explicitTenantAccessBoundary) {
123
147
  obligations.push("tenant-boundary");
124
148
  }
125
149
  }
@@ -674,14 +698,14 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
674
698
  if (semanticConflictReasons(obligation, task, corpus, criterion).length > 0) return undefined;
675
699
 
676
700
  if (obligation === "read-only-evidence") {
701
+ const readOnlyEvidence = durableContextEvidenceEntries(task);
677
702
  const readOnlyOk = task.changeMode === "read-only"
678
703
  && corpus.files.length === 0
679
- && Array.isArray(task.contextManifest)
680
- && task.contextManifest.length > 0;
704
+ && hasDurableContextEvidence(task);
681
705
  if (readOnlyOk) return {
682
706
  kind: "read-only-context",
683
707
  summary: "Read-only context was observed and no project files changed.",
684
- paths: task.contextManifest.map((item) => item.path).slice(0, 8)
708
+ paths: readOnlyEvidence.map((item) => item.path).slice(0, 8)
685
709
  };
686
710
  if (task.changeMode === "source-change") {
687
711
  const boundaryPatterns = uniqueStrings([...(task.outOfScope ?? []), ...(task.protectedPaths ?? [])]);
@@ -695,8 +719,7 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
695
719
  && READ_ONLY_BOUNDARY.test(normalizedText(combinedText))
696
720
  && changedFilesRespectBoundaries(corpus.files, boundaryPatterns)
697
721
  && (boundaryPatterns.length === 0 || pathBoundaryMentioned(combinedText, boundaryPatterns))
698
- && Array.isArray(task.contextManifest)
699
- && task.contextManifest.length > 0;
722
+ && hasDurableContextEvidence(task);
700
723
  if (sourceChangeReadOnlyOk) {
701
724
  return {
702
725
  kind: "source-change-read-only-boundary",
@@ -0,0 +1,518 @@
1
+ import { rejectionStatementErrorClass } from "./acceptance-error-classes.js";
2
+
3
+ function escapeRegex(value) {
4
+ return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+
7
+ function balancedEnd(text, openIndex, opening = "(", closing = ")", ceiling = 8_000) {
8
+ if (text[openIndex] !== opening) return -1;
9
+ let depth = 0;
10
+ for (let index = openIndex; index < Math.min(text.length, openIndex + ceiling); index += 1) {
11
+ if (text[index] === opening) depth += 1;
12
+ else if (text[index] === closing && --depth === 0) return index + 1;
13
+ }
14
+ return -1;
15
+ }
16
+
17
+ function braceDepthAt(text, offset) {
18
+ let depth = 0;
19
+ for (let index = 0; index < offset; index += 1) {
20
+ if (text[index] === "{") depth += 1;
21
+ else if (text[index] === "}") depth = Math.max(0, depth - 1);
22
+ }
23
+ return depth;
24
+ }
25
+
26
+ function priorUnconditionalExit(text, offset) {
27
+ for (const match of text.slice(0, offset).matchAll(/\b(?:return|throw)\b/g)) {
28
+ if (braceDepthAt(text, match.index) !== 0) continue;
29
+ const statementStart = Math.max(text.lastIndexOf(";", match.index - 1), text.lastIndexOf("}", match.index - 1)) + 1;
30
+ if (!text.slice(statementStart, match.index).trim()) return true;
31
+ }
32
+ return false;
33
+ }
34
+
35
+ function topLevelParts(text, delimiter) {
36
+ const parts = [];
37
+ let start = 0;
38
+ let parentheses = 0;
39
+ let brackets = 0;
40
+ for (let index = 0; index < text.length; index += 1) {
41
+ if (text[index] === "(") parentheses += 1;
42
+ else if (text[index] === ")") parentheses = Math.max(0, parentheses - 1);
43
+ else if (text[index] === "[") brackets += 1;
44
+ else if (text[index] === "]") brackets = Math.max(0, brackets - 1);
45
+ else if (text[index] === delimiter && parentheses === 0 && brackets === 0) {
46
+ parts.push(text.slice(start, index));
47
+ start = index + 1;
48
+ }
49
+ }
50
+ parts.push(text.slice(start));
51
+ return parts;
52
+ }
53
+
54
+ function stripOuterParentheses(raw) {
55
+ let value = String(raw ?? "").trim();
56
+ while (value.startsWith("(") && balancedEnd(value, 0) === value.length) value = value.slice(1, -1).trim();
57
+ return value;
58
+ }
59
+
60
+ function staticFalsyToken(raw) {
61
+ const value = stripOuterParentheses(raw);
62
+ return /^(?:false|null|undefined|0|__pi_(?:empty|whitespace)_string_literal__)$/i.test(value)
63
+ || /^(?:true\s*={2,3}\s*false|false\s*={2,3}\s*true)$/i.test(value);
64
+ }
65
+
66
+ function staticPrimitiveComparison(raw) {
67
+ const match = stripOuterParentheses(raw).match(/^(-?\d+(?:\.\d+)?|true|false)\s*(===|==|!==|!=|<=|>=|<|>)\s*(-?\d+(?:\.\d+)?|true|false)$/i);
68
+ if (!match) return undefined;
69
+ const value = (token) => /^(?:true|false)$/i.test(token) ? token.toLowerCase() === "true" : Number(token);
70
+ const left = value(match[1]), right = value(match[3]);
71
+ if (["===", "=="].includes(match[2])) return left === right;
72
+ if (["!==", "!="].includes(match[2])) return left !== right;
73
+ if (match[2] === "<") return left < right;
74
+ if (match[2] === ">") return left > right;
75
+ if (match[2] === "<=") return left <= right;
76
+ return left >= right;
77
+ }
78
+
79
+ function staticallyFalseCondition(raw) {
80
+ const value = stripOuterParentheses(raw);
81
+ return staticFalsyToken(value) || staticPrimitiveComparison(value) === false
82
+ || value.replace(/[()]/g, " ").split("&&").some((part) => staticFalsyToken(part) || staticPrimitiveComparison(part) === false);
83
+ }
84
+
85
+ function staticallyTrueCondition(raw) {
86
+ const value = stripOuterParentheses(raw);
87
+ return /^true$/i.test(value) || staticPrimitiveComparison(value) === true;
88
+ }
89
+
90
+ function sideEffectFreeCondition(raw) {
91
+ const value = String(raw ?? "");
92
+ return !staticallyFalseCondition(value)
93
+ && !/(?<![=!<>])=(?!=|>)|\+\+|--|=>|[,;?]|\b(?:await|delete|new|yield)\b/i.test(value)
94
+ && !/\b[a-z_$][a-z0-9_$]*\s*\(/i.test(value);
95
+ }
96
+
97
+ function previousSignificantCharacter(text, offset) {
98
+ let index = offset - 1;
99
+ while (index >= 0 && /\s/.test(text[index])) index -= 1;
100
+ return text[index] ?? "";
101
+ }
102
+
103
+ function directBindingMutations(text, name) {
104
+ const escaped = escapeRegex(name);
105
+ const mutations = [];
106
+ const suffix = new RegExp(`(?<![a-z0-9_$])${escaped}\\b\\s*(?:=(?!=|>)|\\+\\+|--|(?:\\*\\*|<<|>>>?|&&|\\|\\||\\?\\?|[+*/%&|^-])=)`, "gi");
107
+ for (const match of text.matchAll(suffix)) {
108
+ const previous = previousSignificantCharacter(text, match.index);
109
+ if (match.index === undefined || previous && ".#".includes(previous)) continue;
110
+ mutations.push(match.index);
111
+ }
112
+ const prefix = new RegExp(`(?:\\+\\+|--)\\s*(?<![a-z0-9_$.?])${escaped}\\b`, "gi");
113
+ for (const match of text.matchAll(prefix)) mutations.push(match.index + match[0].toLowerCase().lastIndexOf(name.toLowerCase()));
114
+ const destructured = new RegExp(`(?:\\{[^}\\n]{0,500}\\b${escaped}\\b[^}\\n]{0,500}\\}|\\[[^\\]\\n]{0,500}\\b${escaped}\\b[^\\]\\n]{0,500}\\])\\s*=(?!=|>)|\\bfor(?:\\s+await)?\\s*\\(\\s*${escaped}\\s+(?:of|in)\\b`, "gi");
115
+ for (const match of text.matchAll(destructured)) mutations.push(match.index);
116
+ return [...new Set(mutations)].sort((left, right) => left - right);
117
+ }
118
+
119
+ function declarationInitializesAt(text, offset) {
120
+ return /\b(?:const|let|var)\s*$/i.test(text.slice(Math.max(0, offset - 120), offset));
121
+ }
122
+
123
+ function bindingWrittenBefore(text, name, offset) {
124
+ return directBindingMutations(text.slice(0, offset), name)
125
+ .some((mutation) => !declarationInitializesAt(text, mutation));
126
+ }
127
+
128
+ function bindingDeclared(text, name) {
129
+ const escaped = escapeRegex(name);
130
+ return new RegExp(`\\b(?:class|const|function|let|var)\\s+${escaped}\\b|\\b(?:const|let|var)\\s*[\\[{][^}\\]\\n]{0,500}\\b${escaped}\\b|\\bcatch\\s*\\([^)]*\\b${escaped}\\b|\\bimport\\b[^;\\n]{0,500}\\b${escaped}\\b`, "i").test(text);
131
+ }
132
+
133
+ function memberBindingMutation(text, name) {
134
+ const escaped = escapeRegex(name);
135
+ return new RegExp(`(?<![a-z0-9_$.?])${escaped}\\b\\s*(?:\\.[a-z_$][a-z0-9_$]*|\\[[^\\]]{1,300}\\])\\s*(?:=(?!=|>)|\\+\\+|--|(?:\\*\\*|<<|>>>?|&&|\\|\\||\\?\\?|[+*/%&|^-])=)`, "i").test(text);
136
+ }
137
+
138
+ function memberBindingCall(text, name) {
139
+ return new RegExp(`(?<![a-z0-9_$.?])${escapeRegex(name)}\\b\\s*(?:\\.[a-z_$][a-z0-9_$]*|\\[[^\\]]{1,300}\\])\\s*\\(`, "i").test(text);
140
+ }
141
+
142
+ function liveInputNames(text, names, referenceText, offset) {
143
+ return names.filter((name) => new RegExp(`\\b${escapeRegex(name)}\\b`, "i").test(referenceText)
144
+ && !bindingWrittenBefore(text, name, offset));
145
+ }
146
+
147
+ function topLevelControlBefore(text, offset) {
148
+ for (const match of text.slice(0, offset).matchAll(/\b(?:do|for|if|return|switch|throw|try|while)\b/gi)) {
149
+ if (braceDepthAt(text, match.index) === 0) return true;
150
+ }
151
+ return false;
152
+ }
153
+
154
+ function topLevelControlBetween(text, start, end) {
155
+ const segment = text.slice(start, end);
156
+ for (const match of segment.matchAll(/\b(?:do|for|if|return|switch|throw|try|while)\b/gi)) {
157
+ if (braceDepthAt(segment, match.index) === 0) return true;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ function preLoopStatementsProven(text, offset, carrier) {
163
+ let prefix = text.slice(0, offset), escaped = escapeRegex(carrier);
164
+ prefix = prefix.replace(new RegExp(`\\bconst\\s+${escaped}\\s*=\\s*string\\s*\\(\\s*[a-z_$][a-z0-9_$]*\\s*\\)\\s*(?:;|(?=\\r?\\n))`, "gi"), " ");
165
+ prefix = prefix.replace(/\b(?:let|var)\s+[a-z_$][a-z0-9_$]*(?:\s*:\s*(?:boolean|string))?\s*=\s*(?:false|__pi_empty_string_literal__)\s*(?:;|(?=\r?\n))/gi, " ");
166
+ return /^[\s;]*$/.test(prefix);
167
+ }
168
+
169
+ function trustedCarrier(text, carrier, offset, parameters, sourceCode) {
170
+ const normalized = carrier.toLowerCase();
171
+ const prefix = text.slice(0, offset);
172
+ if (parameters.includes(normalized)) return "";
173
+ const escaped = escapeRegex(normalized);
174
+ const declarations = [...prefix.matchAll(new RegExp(`\\bconst\\s+${escaped}\\s*=\\s*string\\s*\\(\\s*([a-z_$][a-z0-9_$]*)\\s*\\)\\s*(?:;|(?=\\r?\\n))`, "gi"))]
175
+ .filter((match) => braceDepthAt(prefix, match.index) === 0);
176
+ const input = declarations[0]?.[1]?.toLowerCase();
177
+ if (declarations.length !== 1 || !parameters.includes(input) || bindingDeclared(prefix.slice(0, declarations[0].index), input)
178
+ || memberBindingCall(prefix.slice(0, declarations[0].index), input) || memberBindingMutation(prefix, input)
179
+ || parameters.includes("string") || bindingDeclared(sourceCode, "string") || bindingWrittenBefore(sourceCode, "string", sourceCode.length)) return "";
180
+ return bindingWrittenBefore(prefix, normalized, prefix.length)
181
+ || bindingWrittenBefore(prefix, input, prefix.length) || memberBindingMutation(prefix, normalized) ? "" : normalized;
182
+ }
183
+
184
+ function loopEvidence(text, header, offset, parameters, sourceCode) {
185
+ const clauses = topLevelParts(header, ";");
186
+ if (clauses.length !== 3) {
187
+ const iterator = header.match(/^\s*(?:const|let)\s+([a-z_$][a-z0-9_$]*)(?:\s*:\s*[^=;]+)?\s+of\s+([a-z_$][a-z0-9_$]*)\s*$/i);
188
+ const carrier = iterator && trustedCarrier(text, iterator[2], offset, parameters, sourceCode);
189
+ return carrier ? { carrier, counter: "", item: iterator[1].toLowerCase() } : undefined;
190
+ }
191
+ const initializer = stripOuterParentheses(clauses[0]).match(/^(?:let|var)\s+([a-z_$][a-z0-9_$]*)(?:\s*:\s*number)?\s*=\s*0$/i);
192
+ if (!initializer) return undefined;
193
+ const counter = initializer[1].toLowerCase(), escapedCounter = escapeRegex(counter);
194
+ const condition = stripOuterParentheses(clauses[1]);
195
+ const bound = condition.match(new RegExp(`^${escapedCounter}\\s*<\\s*([a-z_$][a-z0-9_$]*)\\s*\\.\\s*length$`, "i"));
196
+ const update = stripOuterParentheses(clauses[2]);
197
+ if (!bound || !new RegExp(`^(?:${escapedCounter}\\s*\\+\\+|\\+\\+\\s*${escapedCounter}|${escapedCounter}\\s*\\+=\\s*1)$`, "i").test(update)) return undefined;
198
+ const carrier = trustedCarrier(text, bound[1], offset, parameters, sourceCode);
199
+ return carrier ? { carrier, counter, item: "" } : undefined;
200
+ }
201
+
202
+ function statementEnd(text, start, ceiling) {
203
+ const candidates = [text.indexOf(";", start), text.indexOf("\n", start), text.indexOf("\r", start)]
204
+ .filter((index) => index >= start && index < ceiling)
205
+ .map((index) => index + 1);
206
+ return candidates.length > 0 ? Math.min(...candidates) : ceiling;
207
+ }
208
+
209
+ function conditionalRanges(text, loop) {
210
+ const ranges = [];
211
+ for (const match of text.slice(loop.start, loop.end).matchAll(/\bif\s*\(/gi)) {
212
+ const conditionStart = loop.start + match.index;
213
+ const conditionOpen = text.indexOf("(", conditionStart);
214
+ const conditionEnd = balancedEnd(text, conditionOpen);
215
+ if (conditionEnd === -1 || conditionEnd > loop.end) continue;
216
+ const condition = text.slice(conditionOpen + 1, conditionEnd - 1);
217
+ let consequentStart = conditionEnd;
218
+ while (/\s/.test(text[consequentStart] ?? "")) consequentStart += 1;
219
+ const consequentEnd = text[consequentStart] === "{"
220
+ ? balancedEnd(text, consequentStart, "{", "}")
221
+ : statementEnd(text, consequentStart, loop.end + 1);
222
+ if (consequentEnd > consequentStart && consequentEnd <= loop.end + 1) {
223
+ ranges.push({ conditionStart, condition, start: consequentStart, end: consequentEnd });
224
+ }
225
+ }
226
+ return ranges;
227
+ }
228
+
229
+ function exactBooleanWrite(text, offset, name) {
230
+ const match = text.slice(offset).match(new RegExp(`^${escapeRegex(name)}\\s*=\\s*(true|false)\\s*(?:;|(?=\\r?\\n|}))`, "i"));
231
+ return match ? { end: offset + match[0].length, value: match[1].toLowerCase() } : null;
232
+ }
233
+
234
+ function branchHasPriorExit(text, start, offset) {
235
+ const branchStart = text[start] === "{" ? start + 1 : start;
236
+ const prefix = text.slice(branchStart, offset);
237
+ for (const match of prefix.matchAll(/\bif\s*\(/gi)) {
238
+ if (braceDepthAt(prefix, match.index) !== 0) continue;
239
+ const open = prefix.indexOf("(", match.index), end = balancedEnd(prefix, open);
240
+ if (end === -1 || !staticallyTrueCondition(prefix.slice(open + 1, end - 1))) continue;
241
+ const following = prefix.slice(end).match(/^\s*(?:\{\s*)?(?:break|continue|return|throw)\b/i);
242
+ if (following) return true;
243
+ }
244
+ for (const match of prefix.matchAll(/\b(?:break|continue|return|throw)\b/g)) {
245
+ if (braceDepthAt(prefix, match.index) !== 0) continue;
246
+ const statementStart = Math.max(prefix.lastIndexOf(";", match.index - 1), prefix.lastIndexOf("}", match.index - 1)) + 1;
247
+ if (!prefix.slice(statementStart, match.index).trim()) return true;
248
+ }
249
+ return false;
250
+ }
251
+
252
+ function affirmativeQuotedTerminalContract(contractText, targetName) {
253
+ const normalized = String(contractText ?? "").normalize("NFD").replace(/[\u0300-\u036f`_*]/g, "").toLowerCase()
254
+ .replace(/\b(is|are|was|were|do|does|did|should|would|could|must|will|shall)n['’]t\b/g, "$1 not").replace(/\bcan(?:not|['’]t)\b/g, "can not").replace(/\bwon['’]t\b/g, "will not").replace(/\bshan['’]t\b/g, "shall not");
255
+ if (!targetName) return false;
256
+ const target = new RegExp(`\\b${escapeRegex(targetName)}\\b`, "i"), clauses = normalized.split(/(?<=[.!?;])\s+|\n+/);
257
+ const negated = (clause) => /\b(?:avoid|do\s+not|does\s+not|fails?\s+to|ignore|must\s+not|never|no|not|should\s+not|without|unchanged|unrelated|out\s+of\s+scope)\b/.test(clause);
258
+ const uniquelyMentionsTarget = (clause) => {
259
+ const calls = [...clause.matchAll(/\b([a-z_$][a-z0-9_$]*)\s*\(/gi)].map((match) => match[1].toLowerCase());
260
+ const escaped = escapeRegex(targetName), other = `(?!${escaped}\\b)[a-z_$][a-z0-9_$]*`, connector = `(?:,|and|or|with|alongside|rather\\s+than|instead\\s+of|versus|vs\\.?)`, peer = new RegExp(`(?:\\b${escaped}\\b(?:\\s*\\([^)]{0,200}\\))?\\s*${connector}\\s*${other}\\b|\\b${other}\\b\\s*${connector}\\s*\\b${escaped}\\b)`, "i");
261
+ return target.test(clause) && !peer.test(clause) && calls.every((name) => name === targetName.toLowerCase());
262
+ };
263
+ const directlyObligatesTarget = (clause) => uniquelyMentionsTarget(clause) && new RegExp(`\\b${escapeRegex(targetName)}\\b(?:\\s*\\([^)]{0,200}\\))?\\s+(?:(?:must|shall|should|will)\\s+)?(?:raise|reject|throw)s?\\b`, "i").test(clause);
264
+ const linkedTargetDirective = (clause) => new RegExp(`^(?:implement|preserve|replace|update)\\s+(?:the\\s+)?(?:(?:function|entrypoint)\\s+)?${escapeRegex(targetName)}(?:\\s*\\([^)]{0,200}\\))?(?:\\s+(?:function|entrypoint))?[.!?;]*$`, "i").test(clause.trim());
265
+ const linkedNeighbor = (index, affirmative) => {
266
+ const neighbors = [clauses[index - 1], clauses[index + 1]].filter((clause) => /^\s*(?:implement|preserve|replace|update)\b/i.test(clause ?? ""));
267
+ return /^\s*(?:(?:must|shall|should)\s+)?(?:raise|reject|throw)s?\b/i.test(affirmative)
268
+ && neighbors.length === 1 && linkedTargetDirective(neighbors[0]) && !negated(neighbors[0]) && uniquelyMentionsTarget(neighbors[0]);
269
+ };
270
+ return clauses.some((clause, index) => (/\bunterminated\b[\s\S]{0,100}\bquoted?\s+field\b|\bquoted?\s+field\b[\s\S]{0,100}\bunterminated\b/.test(clause)
271
+ && /\b(?:raise|reject|throw)s?\b[\s\S]{0,80}\bsyntaxerror\b|\bsyntaxerror\b[\s\S]{0,80}\b(?:raise|reject|throw)s?\b/.test(clause)
272
+ && !negated(clause)
273
+ && (directlyObligatesTarget(clause) || linkedNeighbor(index, clause))));
274
+ }
275
+
276
+ function quoteInputGuard(condition, loop) {
277
+ const operands = [...loop.items.map((name) => `\\b${escapeRegex(name)}\\b`)];
278
+ if (loop.counter) operands.push(`\\b${escapeRegex(loop.carrier)}\\b\\s*\\[\\s*${escapeRegex(loop.counter)}\\s*\\]`);
279
+ let quoteComparisons = 0;
280
+ for (const rawPart of topLevelParts(String(condition ?? "").replaceAll("&&", "\u0000"), "\u0000")) {
281
+ const part = stripOuterParentheses(rawPart);
282
+ const quote = operands.some((operand) => new RegExp(`^(?:${operand}\\s*={3}\\s*__pi_double_quote_string_literal__|__pi_double_quote_string_literal__\\s*={3}\\s*${operand})$`, "i").test(part));
283
+ if (quote) { quoteComparisons += 1; continue; }
284
+ const empty = part.match(/^(?:([a-z_$][a-z0-9_$]*)\s*={3}\s*__pi_empty_string_literal__|__pi_empty_string_literal__\s*={3}\s*([a-z_$][a-z0-9_$]*))$/i);
285
+ if (!empty || !loop.emptyAccumulators.includes((empty[1] ?? empty[2]).toLowerCase())) return false;
286
+ }
287
+ return quoteComparisons === 1;
288
+ }
289
+
290
+ function transitionEvidence(text, loop, ranges, state, item) {
291
+ const ancestors = ranges.filter((range) => item.offset >= range.start && item.write.end <= range.end);
292
+ if (ancestors.length === 0 || ancestors.some((range) => !sideEffectFreeCondition(range.condition)
293
+ || branchHasPriorExit(text, range.start, item.offset)) || branchHasPriorExit(text, loop.start, item.offset)) return undefined;
294
+ if (!ancestors.some((range) => liveInputNames(text, loop.inputNames, range.condition, range.conditionStart).length > 0
295
+ && quoteInputGuard(range.condition, loop))) return undefined;
296
+ const stateGuard = ancestors.some((range) => new RegExp(`^${escapeRegex(state)}(?:\\s*={2,3}\\s*true)?$`, "i").test(stripOuterParentheses(range.condition)));
297
+ const stateGuardBraced = ancestors.some((range) => text[range.start] === "{"
298
+ && new RegExp(`^${escapeRegex(state)}(?:\\s*={2,3}\\s*true)?$`, "i").test(stripOuterParentheses(range.condition)));
299
+ const immediate = [...ancestors].sort((left, right) => (left.end - left.start) - (right.end - right.start))[0];
300
+ return { ...item, immediate, stateGuard, stateGuardBraced };
301
+ }
302
+
303
+ function initiallyFalseAuxiliaryGuard(text, loop, ranges, ancestors, state) {
304
+ for (const ancestor of ancestors) {
305
+ const auxiliary = stripOuterParentheses(ancestor.condition).match(/^([a-z_$][a-z0-9_$]*)(?:\s*={2,3}\s*true)?$/i)?.[1]?.toLowerCase();
306
+ if (!auxiliary || auxiliary === state || auxiliary === loop.counter || loop.inputNames.includes(auxiliary)) continue;
307
+ const declarations = [...text.slice(0, loop.start).matchAll(new RegExp(`\\b(?:let|var)\\s+${escapeRegex(auxiliary)}(?:\\s*:\\s*boolean)?\\s*=\\s*false\\s*(?:;|(?=\\r?\\n))`, "gi"))]
308
+ .filter((match) => braceDepthAt(text, match.index) === 0);
309
+ if (declarations.length !== 1) continue;
310
+ const mutations = directBindingMutations(text.slice(loop.start, ancestor.conditionStart), auxiliary);
311
+ if (mutations.every((relativeOffset) => {
312
+ const offset = loop.start + relativeOffset, write = exactBooleanWrite(text, offset, auxiliary);
313
+ if (!write) return false;
314
+ if (write.value === "false") return true;
315
+ const guards = ranges.filter((range) => offset >= range.start && write.end <= range.end);
316
+ return guards.some((range) => new RegExp(`^${escapeRegex(state)}(?:\\s*={2,3}\\s*true)?$`, "i").test(stripOuterParentheses(range.condition)))
317
+ && guards.some((range) => quoteInputGuard(range.condition, loop));
318
+ })) return true;
319
+ }
320
+ return false;
321
+ }
322
+
323
+ function counterFlowProven(text, loop, ranges, state, truthyOffset) {
324
+ if (!loop.counter) return true;
325
+ const bodyText = text.slice(loop.start, loop.end);
326
+ for (const relativeOffset of directBindingMutations(bodyText, loop.counter)) {
327
+ const offset = loop.start + relativeOffset;
328
+ if (!new RegExp(`^${escapeRegex(loop.counter)}\\s*(?:\\+\\+|\\+=\\s*1)\\s*(?:;|(?=\\r?\\n|}))`, "i").test(text.slice(offset))) return false;
329
+ if (offset >= truthyOffset) continue;
330
+ const ancestors = ranges.filter((range) => offset >= range.start && offset < range.end);
331
+ const stateGuard = ancestors.some((range) => new RegExp(`^${escapeRegex(state)}(?:\\s*={2,3}\\s*true)?$`, "i").test(stripOuterParentheses(range.condition)));
332
+ const nextQuote = ancestors.some((range) => new RegExp(`\\b${escapeRegex(loop.carrier)}\\b\\s*\\[\\s*${escapeRegex(loop.counter)}\\s*\\+\\s*1\\s*\\]\\s*={3}\\s*__pi_double_quote_string_literal__`, "i").test(range.condition));
333
+ if ((!stateGuard || !nextQuote) && !initiallyFalseAuxiliaryGuard(text, loop, ranges, ancestors, state)) return false;
334
+ }
335
+ return true;
336
+ }
337
+
338
+ function priorSiblingControlProven(text, loop, ranges, state, truthyOffset) {
339
+ const loopText = text.slice(loop.start, loop.end), statePattern = new RegExp(`^${escapeRegex(state)}(?:\\s*={2,3}\\s*true)?$`, "i");
340
+ for (const range of ranges.filter((item) => item.conditionStart < truthyOffset
341
+ && braceDepthAt(loopText, item.conditionStart - loop.start) === 0)) {
342
+ if (truthyOffset >= range.start && truthyOffset < range.end) continue;
343
+ if (text[range.start] === "{" && statePattern.test(stripOuterParentheses(range.condition))
344
+ && !/^\s*else\b/i.test(text.slice(range.end))) continue;
345
+ return false;
346
+ }
347
+ return true;
348
+ }
349
+
350
+ function statementFragmentProven(raw, loop) {
351
+ let value = String(raw ?? "").trim();
352
+ if (!value) return true;
353
+ if (/^else\b/i.test(value)) value = value.replace(/^else\b\s*/i, "");
354
+ while (/^if\s*\(/i.test(value)) {
355
+ const open = value.indexOf("("), end = balancedEnd(value, open);
356
+ if (end === -1) return false;
357
+ value = value.slice(end).trim();
358
+ }
359
+ const carrierItem = loop.counter ? `${escapeRegex(loop.carrier)}\\s*\\[\\s*${escapeRegex(loop.counter)}\\s*\\]` : "";
360
+ const inputs = [...loop.items.map(escapeRegex), carrierItem].filter(Boolean).join("|");
361
+ if (/^continue$/i.test(value)) return true;
362
+ if (loop.counter && new RegExp(`^(?:${escapeRegex(loop.counter)}\\s*(?:\\+\\+|\\+=\\s*1)|\\+\\+\\s*${escapeRegex(loop.counter)})$`, "i").test(value)) return true;
363
+ if (loop.booleanBindings.some((name) => new RegExp(`^${escapeRegex(name)}\\s*=\\s*(?:true|false)$`, "i").test(value))) return true;
364
+ if (inputs && loop.emptyAccumulators.some((name) => new RegExp(`^${escapeRegex(name)}\\s*\\+=\\s*(?:${inputs})$`, "i").test(value))) return true;
365
+ return Boolean(loop.counter && loop.items.some((name) => new RegExp(`^const\\s+${escapeRegex(name)}\\s*=\\s*${carrierItem}$`, "i").test(value)));
366
+ }
367
+
368
+ function loopStatementsProven(loopBody, loop) {
369
+ if (/\b(?:break|delete|new|return|throw|typeof|void)\b/i.test(loopBody)) return false;
370
+ if (/\b(?!if\b)[a-z_$][a-z0-9_$]*\s*(?:\?\.\s*)?\(|\b[a-z_$][a-z0-9_$]*\s*__pi_template_literal__|[\])]\s*(?:\?\.\s*)?\(/i.test(loopBody)) return false;
371
+ for (const match of loopBody.matchAll(/;/g)) {
372
+ const start = Math.max(loopBody.lastIndexOf(";", match.index - 1), loopBody.lastIndexOf("{", match.index - 1), loopBody.lastIndexOf("}", match.index - 1)) + 1;
373
+ if (!statementFragmentProven(loopBody.slice(start, match.index), loop)) return false;
374
+ }
375
+ for (const line of loopBody.split(/\r?\n/)) {
376
+ const tail = line.slice(line.lastIndexOf(";") + 1).trim();
377
+ if (!tail || /^[{}]*\s*(?:else\s*)?[{}]*$/i.test(tail)) continue;
378
+ if (/^(?:}\s*)?(?:else\s+)?if\s*\(/i.test(tail)) {
379
+ const open = tail.indexOf("("), end = balancedEnd(tail, open);
380
+ if (end !== -1 && /^[\s{}]*$/.test(tail.slice(end))) continue;
381
+ }
382
+ if (!statementFragmentProven(tail.replace(/^}\s*/, ""), loop)) return false;
383
+ }
384
+ for (const declaration of loopBody.matchAll(/\b(?:const|let|var)\b/gi)) {
385
+ const exactItem = loop.counter && loopBody.slice(declaration.index).match(new RegExp(`^const\\s+[a-z_$][a-z0-9_$]*\\s*=\\s*${escapeRegex(loop.carrier)}\\s*\\[\\s*${escapeRegex(loop.counter)}\\s*\\]\\s*(?:;|(?=\\r?\\n))`, "i"));
386
+ if (!exactItem || braceDepthAt(loopBody, declaration.index) !== 0) return false;
387
+ }
388
+ return true;
389
+ }
390
+
391
+ function loopConditionsProven(ranges, loop, state) {
392
+ const statePattern = new RegExp(`^${escapeRegex(state)}(?:\\s*={2,3}\\s*true)?$`, "i");
393
+ const quote = "__pi_double_quote_string_literal__";
394
+ const next = loop.counter ? `${escapeRegex(loop.carrier)}\\s*\\[\\s*${escapeRegex(loop.counter)}\\s*\\+\\s*1\\s*\\]` : "";
395
+ return ranges.every((range) => {
396
+ const condition = stripOuterParentheses(range.condition);
397
+ if (!sideEffectFreeCondition(condition)) return false;
398
+ if (statePattern.test(condition) || quoteInputGuard(condition, loop)) return true;
399
+ if (next && new RegExp(`^(?:${next}\\s*={3}\\s*${quote}|${quote}\\s*={3}\\s*${next})$`, "i").test(condition)) return true;
400
+ return loop.booleanBindings.some((name) => name !== state && new RegExp(`^(?:!\\s*${escapeRegex(name)}|${escapeRegex(name)}(?:\\s*={2,3}\\s*(?:true|false))?)$`, "i").test(condition));
401
+ });
402
+ }
403
+
404
+ function transitionShapeProven(text, transitions) {
405
+ const truthy = transitions.find((item) => item.write.value === "true");
406
+ const falsy = transitions.find((item) => item.write.value === "false");
407
+ if (!truthy || !falsy) return false;
408
+ if (falsy.offset < truthy.offset && falsy.stateGuardBraced && !truthy.stateGuard) return true;
409
+ if (truthy.offset >= falsy.offset || truthy.stateGuard || falsy.stateGuard) return false;
410
+ return /^\s*else\s*$/i.test(text.slice(truthy.immediate.end, falsy.immediate.conditionStart));
411
+ }
412
+
413
+ function statementConsequentEnd(text, start, ceiling) {
414
+ let offset = start;
415
+ while (/\s/.test(text[offset] ?? "")) offset += 1;
416
+ return text[offset] === "{" ? balancedEnd(text, offset, "{", "}") : statementEnd(text, offset, ceiling);
417
+ }
418
+
419
+ function elseChainEnd(text, start, ceiling) {
420
+ let offset = start;
421
+ while (offset < ceiling) {
422
+ while (/\s/.test(text[offset] ?? "")) offset += 1;
423
+ if (!/^else\b/i.test(text.slice(offset))) return offset;
424
+ offset += 4;
425
+ while (/\s/.test(text[offset] ?? "")) offset += 1;
426
+ if (/^if\b/i.test(text.slice(offset))) {
427
+ const open = text.indexOf("(", offset), end = balancedEnd(text, open);
428
+ if (open === -1 || end === -1 || end > ceiling) return -1;
429
+ offset = statementConsequentEnd(text, end, ceiling);
430
+ } else offset = statementConsequentEnd(text, offset, ceiling);
431
+ if (offset === -1 || offset > ceiling) return -1;
432
+ }
433
+ return offset;
434
+ }
435
+
436
+ function truthyTransitionTailProven(text, loop, transition) {
437
+ if (!/^[\s;}]*$/.test(text.slice(transition.write.end, transition.immediate.end))) return false;
438
+ const chainEnd = elseChainEnd(text, transition.immediate.end, loop.end);
439
+ return chainEnd !== -1 && /^[\s;]*$/.test(text.slice(chainEnd, loop.end));
440
+ }
441
+
442
+ // Prove only one narrow terminal state-machine rejection shape. Callers must
443
+ // still establish stable exports/imports, live assertions, error constructors,
444
+ // the exact current verifier, and a current-tree changed source/test corpus.
445
+ export function statefulTerminalRejectionEvidence(body, parameters, requestedErrors, contractText, targetName, sourceCode) {
446
+ if (!affirmativeQuotedTerminalContract(contractText, targetName)) return false;
447
+ const loops = [];
448
+ for (const match of body.matchAll(/\bfor\s*\(/gi)) {
449
+ if (braceDepthAt(body, match.index) !== 0 || priorUnconditionalExit(body, match.index) || topLevelControlBefore(body, match.index)) continue;
450
+ const conditionOpen = body.indexOf("(", match.index), conditionEnd = balancedEnd(body, conditionOpen);
451
+ if (conditionEnd === -1) continue;
452
+ const header = body.slice(conditionOpen + 1, conditionEnd - 1);
453
+ const evidence = loopEvidence(body, header, match.index, parameters, sourceCode);
454
+ if (!evidence || !preLoopStatementsProven(body, match.index, evidence.carrier)) continue;
455
+ let bodyStart = conditionEnd;
456
+ while (/\s/.test(body[bodyStart] ?? "")) bodyStart += 1;
457
+ if (body[bodyStart] !== "{") continue;
458
+ const bodyEnd = balancedEnd(body, bodyStart, "{", "}");
459
+ const loopBody = body.slice(bodyStart + 1, bodyEnd - 1);
460
+ const nestedCallable = /=>|\b(?:catch|class|do|for|function|switch|try|while)\b|(?:^|[;{}]\s*)(?!(?:else|if)\b)[a-z_$][a-z0-9_$]*\s*\([^)]*\)\s*\{/im;
461
+ if (bodyEnd === -1 || nestedCallable.test(loopBody) || memberBindingMutation(loopBody, evidence.carrier)
462
+ || [evidence.carrier, evidence.counter, evidence.item].filter(Boolean).some((name) => bindingDeclared(loopBody, name))) continue;
463
+ const items = [evidence.item].filter(Boolean), inputNames = [evidence.carrier, ...items];
464
+ if (evidence.counter) {
465
+ for (const declaration of loopBody.matchAll(new RegExp(`\\bconst\\s+([a-z_$][a-z0-9_$]*)\\s*=\\s*${escapeRegex(evidence.carrier)}\\s*\\[\\s*${escapeRegex(evidence.counter)}\\s*\\]\\s*(?:;|(?=\\r?\\n))`, "gi"))) {
466
+ if (braceDepthAt(loopBody, declaration.index) === 0) items.push(declaration[1].toLowerCase());
467
+ }
468
+ }
469
+ inputNames.push(...items);
470
+ const emptyAccumulators = [...body.slice(0, match.index).matchAll(/\b(?:let|var)\s+([a-z_$][a-z0-9_$]*)(?:\s*:\s*string)?\s*=\s*__pi_empty_string_literal__\s*(?:;|(?=\r?\n))/gi)]
471
+ .filter((item) => braceDepthAt(body, item.index) === 0).map((item) => item[1].toLowerCase());
472
+ const booleanBindings = [...body.slice(0, match.index).matchAll(/\b(?:let|var)\s+([a-z_$][a-z0-9_$]*)(?:\s*:\s*boolean)?\s*=\s*false\s*(?:;|(?=\r?\n))/gi)]
473
+ .filter((item) => braceDepthAt(body, item.index) === 0).map((item) => item[1].toLowerCase());
474
+ const loop = { ...evidence, start: bodyStart + 1, end: bodyEnd - 1, items: [...new Set(items)], inputNames: [...new Set(inputNames)], emptyAccumulators, booleanBindings };
475
+ if (loopStatementsProven(loopBody, loop)) loops.push(loop);
476
+ }
477
+ if (loops.length === 0) return false;
478
+
479
+ for (const declaration of body.matchAll(/\b(?:let|var)\s+([a-z_$][a-z0-9_$]*)(?:\s*:\s*boolean)?\s*=\s*false\s*(?:;|(?=\r?\n))/gi)) {
480
+ if (braceDepthAt(body, declaration.index) !== 0) continue;
481
+ const state = declaration[1].toLowerCase(), escaped = escapeRegex(state);
482
+ if (parameters.includes(state)
483
+ || [...body.matchAll(new RegExp(`\\b(?:const|let|var)\\s+${escaped}\\b`, "gi"))].length !== 1) continue;
484
+ const mutations = directBindingMutations(body, state)
485
+ .filter((offset) => offset < declaration.index || offset >= declaration.index + declaration[0].length);
486
+ const writes = mutations.map((offset) => ({ offset, write: exactBooleanWrite(body, offset, state) }));
487
+ if (writes.length !== 2 || writes.some((item) => !item.write)
488
+ || !writes.some((item) => item.write.value === "true") || !writes.some((item) => item.write.value === "false")) continue;
489
+ const loop = loops.find((candidate) => candidate.start > declaration.index + declaration[0].length
490
+ && writes.every((item) => item.offset >= candidate.start && item.write.end <= candidate.end + 1));
491
+ if (!loop) continue;
492
+ const ranges = conditionalRanges(body, loop);
493
+ const transitions = writes.map((item) => transitionEvidence(body, loop, ranges, state, item));
494
+ const truthyOffset = writes.find((item) => item.write.value === "true").offset;
495
+ const truthyTransition = transitions.find((item) => item?.write.value === "true");
496
+ if (!loopConditionsProven(ranges, loop, state) || transitions.some((item) => !item) || !transitionShapeProven(body, transitions)
497
+ || !counterFlowProven(body, loop, ranges, state, truthyOffset)
498
+ || !priorSiblingControlProven(body, loop, ranges, state, truthyOffset)
499
+ || !truthyTransitionTailProven(body, loop, truthyTransition)) continue;
500
+
501
+ for (const terminal of body.matchAll(/\bif\s*\(/gi)) {
502
+ if (terminal.index <= loop.end || braceDepthAt(body, terminal.index) !== 0 || priorUnconditionalExit(body, terminal.index)
503
+ || topLevelControlBetween(body, loop.end + 1, terminal.index)
504
+ || !/^[\s;]*$/.test(body.slice(loop.end + 1, terminal.index))) continue;
505
+ const conditionOpen = body.indexOf("(", terminal.index), conditionEnd = balancedEnd(body, conditionOpen);
506
+ if (conditionEnd === -1) continue;
507
+ const condition = stripOuterParentheses(body.slice(conditionOpen + 1, conditionEnd - 1));
508
+ if (!new RegExp(`^${escaped}(?:\\s*={2,3}\\s*true)?$`, "i").test(condition)) continue;
509
+ let consequentStart = conditionEnd;
510
+ while (/\s/.test(body[consequentStart] ?? "")) consequentStart += 1;
511
+ const consequentEnd = body[consequentStart] === "{"
512
+ ? balancedEnd(body, consequentStart, "{", "}")
513
+ : statementEnd(body, consequentStart, body.length);
514
+ if (consequentEnd !== -1 && rejectionStatementErrorClass(body.slice(consequentStart, consequentEnd).replace(/^\s*\{/, ""), requestedErrors)) return true;
515
+ }
516
+ }
517
+ return false;
518
+ }