@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.
- package/CHANGELOG.md +117 -0
- package/README.md +9 -8
- package/README.vi.md +9 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +68 -0
- package/benchmarks/production-v1/suite.json +42 -3
- package/catalog/capabilities.json +6 -6
- package/docs/command-reference-vietnamese.md +6 -6
- package/docs/context-engine.md +7 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +1 -0
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +13 -11
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +5 -0
- package/docs/quality-benchmark.md +271 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v4.json +6 -5
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/package.json +3 -1
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +148 -145
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
- package/packages/piagent-core/extensions/criterion-graph.js +78 -8
- package/packages/piagent-core/extensions/guard-types.ts +2 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
- package/packages/piagent-core/extensions/task-state.js +40 -134
- package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +2 -0
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
- package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
- package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
- package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-worker.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +22 -4
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/task-contract.schema.json +12 -1
- package/scripts/benchmark-runner-core.mjs +355 -359
- package/scripts/benchmark-runner-finalization.mjs +309 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/helper-evaluation.mjs +1 -1
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/setup.sh +2 -2
- package/scripts/verify-local.sh +5 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { acceptanceBoundaryProofGuidance } from "./acceptance-boundary-guidance.js";
|
|
3
3
|
import { evidenceTopLevelArguments, executableRejectionAssertions } from "./acceptance-executable-evidence.js";
|
|
4
|
-
|
|
4
|
+
import { ERROR_CONSTRUCTORS, ERROR_CONSTRUCTOR_DISPLAY_NAMES, errorMappingsProveContract,
|
|
5
|
+
hasAmbiguousErrorClassIntent, rejectionStatementErrorClass, requestedErrorClasses, requestedErrorPartitionMapping } from "./acceptance-error-classes.js";
|
|
6
|
+
import { statefulTerminalRejectionEvidence } from "./acceptance-state-machine-evidence.js";
|
|
7
|
+
import { boundRejectionTestEvidence, callableAssertionMode } from "./acceptance-test-binding-evidence.js";
|
|
8
|
+
import { regexCanStartAfterLexicalChunks } from "./javascript-regex-evidence.js";
|
|
5
9
|
const INTEGER_TARGET_STOPWORDS = new Set([
|
|
6
10
|
"and", "basis", "cents", "input", "inputs", "items", "money", "number", "numbers", "or", "points", "typeerror", "value", "values"
|
|
7
11
|
]);
|
|
@@ -19,6 +23,7 @@ function erasedLexeme(value) {
|
|
|
19
23
|
|
|
20
24
|
function stringLiteralSentinel(value) {
|
|
21
25
|
if (value.length === 0) return "__pi_empty_string_literal__";
|
|
26
|
+
if (value === '"') return "__pi_double_quote_string_literal__";
|
|
22
27
|
if (/^(?:\s|\\[nrtvf0])+$/u.test(value)) return "__pi_whitespace_string_literal__";
|
|
23
28
|
if (["assert", "assert/strict", "node:assert", "node:assert/strict"].includes(value.trim().toLowerCase())) {
|
|
24
29
|
return "__pi_node_assert_module_literal__";
|
|
@@ -30,14 +35,6 @@ function stringLiteralSentinel(value) {
|
|
|
30
35
|
return "__pi_string_literal__";
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
function regexCanStart(source, offset) {
|
|
34
|
-
const prefix = source.slice(0, offset).replace(/\s+$/u, "");
|
|
35
|
-
if (!prefix) return true;
|
|
36
|
-
const previous = prefix.at(-1);
|
|
37
|
-
if (/[([{,:;=!?&|+*%^~<>-]/u.test(previous)) return true;
|
|
38
|
-
return /\b(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield)$/u.test(prefix);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
38
|
/**
|
|
42
39
|
* Return a bounded lexical evidence view of JavaScript/TypeScript source.
|
|
43
40
|
*
|
|
@@ -118,7 +115,7 @@ function lexJavaScriptEvidence(value, bindStrings = false) {
|
|
|
118
115
|
index = end;
|
|
119
116
|
continue;
|
|
120
117
|
}
|
|
121
|
-
if (current === "/" && next !== "=" &&
|
|
118
|
+
if (current === "/" && next !== "=" && regexCanStartAfterLexicalChunks(output)) {
|
|
122
119
|
let end = index + 1;
|
|
123
120
|
let inClass = false;
|
|
124
121
|
let closed = false;
|
|
@@ -160,6 +157,7 @@ export function sanitizeJavaScriptEvidence(value) {
|
|
|
160
157
|
function uniqueStrings(values) {
|
|
161
158
|
return [...new Set((Array.isArray(values) ? values : []).filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()))];
|
|
162
159
|
}
|
|
160
|
+
function hasReservedEvidenceIdentifier(value) { return /\b__pi_[a-z0-9_]*\b/i.test(String(value ?? "")); }
|
|
163
161
|
|
|
164
162
|
function escapeRegex(value) {
|
|
165
163
|
return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -207,8 +205,6 @@ function nullRejectingDefaultTargets(text, sourceText = "") {
|
|
|
207
205
|
return uniqueTargets.filter((target) => new RegExp(`(?:\\.${escapeRegex(target)}\\b|\\b${escapeRegex(target)}\\b)\\s*\\?\\?`, "i").test(source));
|
|
208
206
|
}
|
|
209
207
|
|
|
210
|
-
const ERROR_CONSTRUCTORS = ["typeerror", "rangeerror", "syntaxerror", "referenceerror", "urierror", "evalerror", "aggregateerror", "error"];
|
|
211
|
-
|
|
212
208
|
function evidenceBalancedEnd(text, openIndex, opening = "(", closing = ")", ceiling = 8_000) {
|
|
213
209
|
if (text[openIndex] !== opening) return -1;
|
|
214
210
|
let depth = 0;
|
|
@@ -219,13 +215,6 @@ function evidenceBalancedEnd(text, openIndex, opening = "(", closing = ")", ceil
|
|
|
219
215
|
return -1;
|
|
220
216
|
}
|
|
221
217
|
|
|
222
|
-
function requestedErrorClasses(text) {
|
|
223
|
-
const value = normalizedText(text);
|
|
224
|
-
const specific = ERROR_CONSTRUCTORS.slice(0, -1).filter((name) => new RegExp(`\\b${name}\\b`).test(value));
|
|
225
|
-
if (specific.length > 0) return specific;
|
|
226
|
-
return /\b(?:throw(?:s|ing)?|reject(?:s|ed|ion)?)\b[^.\n]{0,80}\berror\b/.test(value) ? ["error"] : [];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
218
|
function simpleConstantValue(raw) {
|
|
230
219
|
let value = String(raw ?? "").trim();
|
|
231
220
|
while (value.startsWith("(") && evidenceBalancedEnd(value, 0) === value.length) value = value.slice(1, -1).trim();
|
|
@@ -423,15 +412,24 @@ function namedTargetBindings(sourceEntries, testEntries, namedTargets) {
|
|
|
423
412
|
return namedTargets.map((target) => {
|
|
424
413
|
const matches = exports.filter((item) => item.contractName === target
|
|
425
414
|
&& sourceCallableBindingIsStable(sourceEntries.find((entry) => entryPath(entry?.path) === item.sourcePath), item.sourceName));
|
|
426
|
-
if (matches.length !== 1) return { target, sourcePath: null, sourceName: null, testNames: [] };
|
|
415
|
+
if (matches.length !== 1) return { target, sourcePath: null, sourceName: null, testNames: [], testBindings: [] };
|
|
427
416
|
const exported = matches[0];
|
|
428
|
-
const
|
|
417
|
+
const testBindings = [];
|
|
429
418
|
for (const binding of imports.filter((item) => importResolvesTo(item, exported.sourcePath))) {
|
|
430
419
|
const testName = binding.importName === "*" ? `${binding.localName}.${exported.exportName}` : binding.localName;
|
|
431
420
|
if ((binding.importName === exported.exportName || binding.importName === "*")
|
|
432
|
-
&& !importedBindingIsShadowed(entries.get(binding.testPath), testName))
|
|
421
|
+
&& !importedBindingIsShadowed(entries.get(binding.testPath), testName)) {
|
|
422
|
+
testBindings.push({ testName, testPath: binding.testPath });
|
|
423
|
+
}
|
|
433
424
|
}
|
|
434
|
-
|
|
425
|
+
const uniqueBindings = [...new Map(testBindings.map((item) => [`${item.testPath}\u0000${item.testName}`, item])).values()];
|
|
426
|
+
return {
|
|
427
|
+
target,
|
|
428
|
+
sourcePath: exported.sourcePath,
|
|
429
|
+
sourceName: exported.sourceName,
|
|
430
|
+
testNames: uniqueStrings(uniqueBindings.map((item) => item.testName)),
|
|
431
|
+
testBindings: uniqueBindings
|
|
432
|
+
};
|
|
435
433
|
});
|
|
436
434
|
}
|
|
437
435
|
|
|
@@ -461,25 +459,23 @@ function formalParameterNames(parameters) {
|
|
|
461
459
|
|
|
462
460
|
function callableBodies(code) {
|
|
463
461
|
const bodies = new Map();
|
|
464
|
-
const addBraced = (name, parameters, open) => {
|
|
462
|
+
const addBraced = (name, parameters, open, asynchronous = false) => {
|
|
465
463
|
const end = evidenceBalancedEnd(code, open, "{", "}");
|
|
466
464
|
if (end !== -1 && !bodies.has(name)) bodies.set(name, {
|
|
467
465
|
body: code.slice(open + 1, end - 1),
|
|
468
|
-
parameters: formalParameterNames(parameters),
|
|
469
|
-
sourceCode: code
|
|
466
|
+
parameters: formalParameterNames(parameters), sourceCode: code, asynchronous
|
|
470
467
|
});
|
|
471
468
|
};
|
|
472
|
-
for (const match of code.matchAll(/\
|
|
469
|
+
for (const match of code.matchAll(/\b(?:async\s+)?function\s+([a-z_$][a-z0-9_$]*)\s*\(([^)]*)\)\s*\{/gi)) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"), /^async\b/i.test(match[0]));
|
|
473
470
|
for (const match of code.matchAll(/^\s*(?:async\s+)?([a-z_$][a-z0-9_$]*)\s*\(([^)]*)\)\s*\{/gim)) {
|
|
474
|
-
if (!["catch", "for", "if", "switch", "while", "with"].includes(match[1].toLowerCase())) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"));
|
|
471
|
+
if (!["catch", "for", "if", "switch", "while", "with"].includes(match[1].toLowerCase())) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"), /^\s*async\b/i.test(match[0]));
|
|
475
472
|
}
|
|
476
473
|
for (const match of code.matchAll(/\b(?:const|let|var)\s+([a-z_$][a-z0-9_$]*)\s*=\s*(?:async\s*)?(?:\(([^)]*)\)|([a-z_$][a-z0-9_$]*))\s*=>\s*/gi)) {
|
|
477
474
|
const start = match.index + match[0].length;
|
|
478
|
-
if (code[start] === "{") addBraced(match[1].toLowerCase(), match[2] ?? match[3], start);
|
|
475
|
+
if (code[start] === "{") addBraced(match[1].toLowerCase(), match[2] ?? match[3], start, /=\s*async\b/i.test(match[0]));
|
|
479
476
|
else bodies.set(match[1].toLowerCase(), {
|
|
480
477
|
body: code.slice(start, [code.indexOf(";", start), code.indexOf("\n", start)].filter((item) => item >= 0).sort((a, b) => a - b)[0] ?? code.length),
|
|
481
|
-
parameters: formalParameterNames(match[2] ?? match[3]),
|
|
482
|
-
sourceCode: code
|
|
478
|
+
parameters: formalParameterNames(match[2] ?? match[3]), sourceCode: code, asynchronous: /=\s*async\b/i.test(match[0])
|
|
483
479
|
});
|
|
484
480
|
}
|
|
485
481
|
return bodies;
|
|
@@ -624,12 +620,6 @@ function validationConditionProof(condition, names, bodies) {
|
|
|
624
620
|
return proof;
|
|
625
621
|
}
|
|
626
622
|
|
|
627
|
-
function rejectionStatementProves(text, requestedErrors) {
|
|
628
|
-
return requestedErrors.length === 0
|
|
629
|
-
? /^\s*throw\s+(?:new\s+)?[a-z_$][a-z0-9_$]*(?:\s*\(|\b)/.test(text)
|
|
630
|
-
: requestedErrors.some((name) => new RegExp(`^\\s*throw\\s+(?:new\\s+)?${escapeRegex(name)}\\s*\\(`).test(text));
|
|
631
|
-
}
|
|
632
|
-
|
|
633
623
|
function exactHelperCall(text) {
|
|
634
624
|
let value = String(text ?? "").trim();
|
|
635
625
|
if (value.startsWith("{") && evidenceBalancedEnd(value, 0, "{", "}") === value.length) {
|
|
@@ -721,8 +711,8 @@ function inputNameWrittenBefore(callable, name, offset) {
|
|
|
721
711
|
return new RegExp(`(?:\\+\\+|--)\\s*\\b${escaped}\\b`, "i").test(prefix);
|
|
722
712
|
}
|
|
723
713
|
|
|
724
|
-
function conditionalRejection(callable, requestedErrors, bodies) {
|
|
725
|
-
const proof = { generic: false, partitions: new Set() };
|
|
714
|
+
function conditionalRejection(callable, requestedErrors, bodies, contractText = "", contractTarget = "") {
|
|
715
|
+
const proof = { generic: false, partitions: new Set(), errorMappings: [] };
|
|
726
716
|
const names = inputDerivedNames(callable);
|
|
727
717
|
for (const match of callable.body.matchAll(/\bif\s*\(/g)) {
|
|
728
718
|
if (sourceBraceDepthAt(callable.body, match.index) !== 0 || priorUnconditionalExit(callable.body, match.index)) continue;
|
|
@@ -737,8 +727,11 @@ function conditionalRejection(callable, requestedErrors, bodies) {
|
|
|
737
727
|
const consequent = callable.body.slice(consequentStart, consequentEnd === -1 ? callable.body.length : consequentEnd);
|
|
738
728
|
const condition = callable.body.slice(conditionOpen + 1, conditionEnd - 1);
|
|
739
729
|
const liveNames = names.filter((name) => !inputNameWrittenBefore(callable, name, match.index));
|
|
740
|
-
|
|
741
|
-
|
|
730
|
+
const consequentError = rejectionStatementErrorClass(consequent.replace(/^\s*\{/, ""), requestedErrors);
|
|
731
|
+
if (consequentError) {
|
|
732
|
+
const validation = validationConditionProof(condition, liveNames, bodies);
|
|
733
|
+
mergeValidationProof(proof, validation);
|
|
734
|
+
for (const partition of validation.partitions) proof.errorMappings.push({ partition, errorClass: consequentError });
|
|
742
735
|
continue;
|
|
743
736
|
}
|
|
744
737
|
const throwingHelper = exactHelperCall(consequent);
|
|
@@ -749,18 +742,25 @@ function conditionalRejection(callable, requestedErrors, bodies) {
|
|
|
749
742
|
}
|
|
750
743
|
if (!/^\s*\{?\s*return\b/.test(consequent)) continue;
|
|
751
744
|
const following = callable.body.slice(consequentEnd === -1 ? callable.body.length : consequentEnd);
|
|
752
|
-
|
|
753
|
-
|
|
745
|
+
const followingError = rejectionStatementErrorClass(following, requestedErrors);
|
|
746
|
+
if (followingError) {
|
|
747
|
+
const validation = positiveValidationConditionProof(condition, liveNames, bodies);
|
|
748
|
+
mergeValidationProof(proof, validation);
|
|
749
|
+
for (const partition of validation.partitions) proof.errorMappings.push({ partition, errorClass: followingError });
|
|
754
750
|
}
|
|
755
751
|
}
|
|
752
|
+
proof.generic ||= statefulTerminalRejectionEvidence(callable.body, callable.parameters, requestedErrors, contractText, contractTarget, callable.sourceCode);
|
|
756
753
|
return { ...proof, names };
|
|
757
754
|
}
|
|
758
|
-
|
|
759
|
-
|
|
755
|
+
function sourceCallableProvesErrorMapping(bodies, name, mapping) {
|
|
756
|
+
const observed = sourceCallableProof(bodies, name, []).errorMappings ?? [];
|
|
757
|
+
return errorMappingsProveContract(observed, mapping);
|
|
758
|
+
}
|
|
759
|
+
function sourceCallableProof(bodies, name, requestedErrors, visited = new Set(), contractText = "", contractTarget = name) {
|
|
760
760
|
if (visited.has(name) || visited.size >= 12) return { generic: false, partitions: new Set() };
|
|
761
761
|
const callable = bodies.get(name);
|
|
762
762
|
if (!callable) return { generic: false, partitions: new Set() };
|
|
763
|
-
const proof = conditionalRejection(callable, requestedErrors, bodies);
|
|
763
|
+
const proof = conditionalRejection(callable, requestedErrors, bodies, contractText, contractTarget);
|
|
764
764
|
const nextVisited = new Set(visited).add(name);
|
|
765
765
|
for (const match of callable.body.matchAll(/\b([a-z_$][a-z0-9_$]*)\s*\(/gi)) {
|
|
766
766
|
const helper = match[1].toLowerCase();
|
|
@@ -770,17 +770,17 @@ function sourceCallableProof(bodies, name, requestedErrors, visited = new Set())
|
|
|
770
770
|
const argumentsText = end === -1 ? "" : callable.body.slice(open + 1, end - 1);
|
|
771
771
|
if (!proof.names.some((candidate) => !inputNameWrittenBefore(callable, candidate, match.index)
|
|
772
772
|
&& new RegExp(`\\b${escapeRegex(candidate)}\\b`).test(argumentsText))) continue;
|
|
773
|
-
const helperProof = sourceCallableProof(bodies, helper, requestedErrors, nextVisited);
|
|
773
|
+
const helperProof = sourceCallableProof(bodies, helper, requestedErrors, nextVisited, contractText, contractTarget);
|
|
774
774
|
proof.generic ||= helperProof.generic;
|
|
775
775
|
for (const partition of helperProof.partitions) proof.partitions.add(partition);
|
|
776
776
|
}
|
|
777
777
|
return proof;
|
|
778
778
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const proof = sourceCallableProof(bodies, name, requestedErrors);
|
|
779
|
+
function sourceCallableProves(bodies, name, requestedErrors, requestedPartitions, contractText = "") {
|
|
780
|
+
const proof = sourceCallableProof(bodies, name, requestedErrors, new Set(), contractText, name);
|
|
782
781
|
return proof.generic && requestedPartitions.every((partition) => partitionCovered(partition, proof.partitions));
|
|
783
782
|
}
|
|
783
|
+
|
|
784
784
|
function evidenceCallableNames(sourceText) {
|
|
785
785
|
const names = new Set();
|
|
786
786
|
const ignored = new Set(["catch", "for", "if", "switch", "while", "with"]);
|
|
@@ -891,7 +891,12 @@ export function acceptanceInvalidInputEvidence(input = {}) {
|
|
|
891
891
|
const sourceText = normalizedText(sanitizeJavaScriptEvidence(input.sourceText));
|
|
892
892
|
const testText = normalizedText(sanitizeJavaScriptEvidence(input.testText));
|
|
893
893
|
const requestedErrors = requestedErrorClasses(taskText);
|
|
894
|
+
const ambiguousErrorIntent = hasAmbiguousErrorClassIntent(taskText);
|
|
894
895
|
const requestedPartitions = requestedInvalidPartitions(taskText);
|
|
896
|
+
// A constructor set alone cannot prove a mapping. Mixed-class evidence uses a narrow grammar;
|
|
897
|
+
// ambiguous prose or indirect source/test proof abstains fail-closed.
|
|
898
|
+
const requestedErrorMapping = requestedErrors.length > 1
|
|
899
|
+
? requestedErrorPartitionMapping(taskText, requestedErrors, requestedPartitions) : null;
|
|
895
900
|
const namedTargets = uniqueStrings(input.namedTargets).map((item) => item.toLowerCase());
|
|
896
901
|
const provenanceTargets = new Set(uniqueStrings(input.provenanceTargets).map((item) => item.toLowerCase()));
|
|
897
902
|
const sourceEntries = (Array.isArray(input.sourceEntries) ? input.sourceEntries : [])
|
|
@@ -900,8 +905,8 @@ export function acceptanceInvalidInputEvidence(input = {}) {
|
|
|
900
905
|
.filter((entry) => entry && typeof entry.path === "string" && typeof entry.text === "string");
|
|
901
906
|
const bodyMaps = new Map(sourceEntries.map((entry) => [entryPath(entry.path), callableBodies(normalizedText(sanitizeJavaScriptEvidence(entry.text)))]));
|
|
902
907
|
if (sourceEntries.length === 0) bodyMaps.set("", callableBodies(sourceText));
|
|
903
|
-
const sourceLexicalOk = sourceEntries.length > 0 ? sourceEntries.every((entry) => lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : lexicalEvidenceIsNonReflective(sourceText);
|
|
904
|
-
const testLexicalOk = testEntries.length > 0 ? testEntries.every((entry) => lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : lexicalEvidenceIsNonReflective(testText);
|
|
908
|
+
const sourceLexicalOk = sourceEntries.length > 0 ? sourceEntries.every((entry) => !hasReservedEvidenceIdentifier(entry.text) && lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : !hasReservedEvidenceIdentifier(input.sourceText) && lexicalEvidenceIsNonReflective(sourceText);
|
|
909
|
+
const testLexicalOk = testEntries.length > 0 ? testEntries.every((entry) => !hasReservedEvidenceIdentifier(entry.text) && lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : !hasReservedEvidenceIdentifier(input.testText) && lexicalEvidenceIsNonReflective(testText);
|
|
905
910
|
const sourceConstructorOk = requestedErrors.length === 0 || (sourceEntries.length > 0
|
|
906
911
|
? sourceEntries.every((entry) => intrinsicErrorBindingsUntampered(normalizedText(sanitizeJavaScriptEvidence(entry.text)), requestedErrors))
|
|
907
912
|
: intrinsicErrorBindingsUntampered(sourceText, requestedErrors));
|
|
@@ -922,35 +927,44 @@ export function acceptanceInvalidInputEvidence(input = {}) {
|
|
|
922
927
|
&& (requestedErrors.length === 0 || requestedErrors.some((name) => assertion.errorClasses.includes(name))))
|
|
923
928
|
.map((assertion) => ({ assertion, binding }));
|
|
924
929
|
});
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
const targetOk =
|
|
933
|
-
bindings.every((binding) => binding.testNames.length > 0 && binding.testNames.some((name) => assertions.some((assertion) => assertion.targets.includes(name))))
|
|
934
|
-
&& structuralTargets.every((target) => assertions.some((assertion) => assertion.targets.includes(`*.${target}`)))
|
|
935
|
-
));
|
|
930
|
+
const boundTestEvidence = boundRejectionTestEvidence({
|
|
931
|
+
bindings, structuralTargets, requestedErrors, requestedErrorMapping,
|
|
932
|
+
testCodeEntries: testEntries.map((entry) => ({ path: entryPath(entry.path), code: normalizedText(sanitizeJavaScriptEvidence(entry.text)) })),
|
|
933
|
+
fallbackTestCode: testText, bodyMaps
|
|
934
|
+
});
|
|
935
|
+
const assertions = namedTargets.length > 0 ? boundTestEvidence.assertions : inferredAssertions.map((item) => item.assertion);
|
|
936
|
+
const mappingTestOk = namedTargets.length > 0 ? boundTestEvidence.mappingOk : requestedErrors.length <= 1;
|
|
937
|
+
const targetOk = namedTargets.length > 0 ? boundTestEvidence.targetOk : assertions.length > 0;
|
|
936
938
|
const sourceOk = sourceLexicalOk && sourceConstructorOk && (namedTargets.length > 0
|
|
937
939
|
? bindings.length === strictTargets.length
|
|
938
|
-
&& bindings.every((binding) => Boolean(binding.sourcePath) && Boolean(binding.sourceName) && sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions))
|
|
940
|
+
&& bindings.every((binding) => Boolean(binding.sourcePath) && Boolean(binding.sourceName) && sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions, taskText))
|
|
939
941
|
&& structuralTargets.every((target) => [...bodyMaps.values()].filter((bodies) => bodies.has(target)).length === 1
|
|
940
|
-
&& [...bodyMaps.values()].some((bodies) => sourceCallableProves(bodies, target, requestedErrors, requestedPartitions)))
|
|
942
|
+
&& [...bodyMaps.values()].some((bodies) => sourceCallableProves(bodies, target, requestedErrors, requestedPartitions, taskText)))
|
|
941
943
|
: inferredAssertions.length > 0 && inferredAssertions.every(({ binding }) => (
|
|
942
|
-
sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions)
|
|
943
|
-
)))
|
|
944
|
+
sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions, taskText)
|
|
945
|
+
))) && (requestedErrors.length <= 1 || Boolean(requestedErrorMapping && namedTargets.length > 0
|
|
946
|
+
&& bindings.every((binding) => sourceCallableProvesErrorMapping(
|
|
947
|
+
bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrorMapping
|
|
948
|
+
))
|
|
949
|
+
&& structuralTargets.every((target) => [...bodyMaps.values()].filter((bodies) => bodies.has(target)).length === 1
|
|
950
|
+
&& [...bodyMaps.values()].some((bodies) => sourceCallableProvesErrorMapping(bodies, target, requestedErrorMapping)))));
|
|
951
|
+
const assertionModeOk = namedTargets.length > 0 ? boundTestEvidence.modeOk
|
|
952
|
+
: inferredAssertions.length > 0 && inferredAssertions.every(({ assertion, binding }) => (
|
|
953
|
+
assertion.mode === callableAssertionMode(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName)
|
|
954
|
+
));
|
|
944
955
|
const partitions = new Set(assertions.flatMap((assertion) => [...assertion.partitions]));
|
|
945
956
|
const partitionOk = requestedPartitions.every((partition) => partitionCovered(partition, partitions));
|
|
946
|
-
return { sourceOk
|
|
957
|
+
return { sourceOk: sourceOk && !ambiguousErrorIntent,
|
|
958
|
+
testOk: testLexicalOk && testConstructorOk && targetOk && partitionOk && mappingTestOk && assertionModeOk && !ambiguousErrorIntent };
|
|
947
959
|
}
|
|
948
960
|
|
|
949
961
|
export function acceptanceContractProofGuidance(raw) {
|
|
950
962
|
const value = normalizedText(raw);
|
|
951
963
|
const guidance = [...acceptanceBoundaryProofGuidance(raw)];
|
|
952
964
|
if (onlyUndefinedContract(raw)) guidance.push("Prove undefined falls through while null, false, 0, and empty string are each preserved at the highest-precedence position.");
|
|
953
|
-
|
|
965
|
+
const requestedSpecificErrors = hasAmbiguousErrorClassIntent(raw) ? [] : requestedErrorClasses(raw).map((errorClass) => ERROR_CONSTRUCTOR_DISPLAY_NAMES[errorClass]).filter(Boolean);
|
|
966
|
+
if (requestedSpecificErrors.length === 1) guidance.push(`Assert ${requestedSpecificErrors[0]} for every rejected partition named by the request; a different error class is not equivalent.`);
|
|
967
|
+
if (requestedSpecificErrors.length > 1) guidance.push(`Preserve the request's partition-to-error mapping: assert each rejected partition with its explicitly named class (${requestedSpecificErrors.join(", ")}); do not apply one class to every partition.`);
|
|
954
968
|
const integerTargets = integerConstraintTargets(raw);
|
|
955
969
|
if (integerTargets.length > 0) guidance.push(`Reject fractional values for every integer-constrained argument (${integerTargets.join(", ")}).`);
|
|
956
970
|
const nullRejectingTargets = nullRejectingDefaultTargets(raw);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const ERROR_CONSTRUCTORS = [
|
|
2
|
+
"typeerror",
|
|
3
|
+
"rangeerror",
|
|
4
|
+
"syntaxerror",
|
|
5
|
+
"referenceerror",
|
|
6
|
+
"urierror",
|
|
7
|
+
"evalerror",
|
|
8
|
+
"aggregateerror",
|
|
9
|
+
"error"
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export const ERROR_CONSTRUCTOR_DISPLAY_NAMES = Object.freeze({
|
|
13
|
+
typeerror: "TypeError",
|
|
14
|
+
rangeerror: "RangeError",
|
|
15
|
+
syntaxerror: "SyntaxError",
|
|
16
|
+
referenceerror: "ReferenceError",
|
|
17
|
+
urierror: "URIError",
|
|
18
|
+
evalerror: "EvalError",
|
|
19
|
+
aggregateerror: "AggregateError",
|
|
20
|
+
error: "Error"
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const ERROR_ACTION = String.raw`(?:throw(?:s|ing)?|rais(?:e|es|ed|ing)|reject(?:s|ed|ing)?|use(?:s|d|ing)?|return(?:s|ed|ing)?|emit(?:s|ted|ting)?|produc(?:e|es|ed|ing)|expect(?:s|ed|ing)?|requir(?:e|es|ed|ing))`;
|
|
24
|
+
const ERROR_ACTION_MENTION = new RegExp(String.raw`\b(?:(?:(?:must|should|shall|do|does|did|can|could|may|might|will|would)\s+)?(?:not|never)\s+(?:ever\s+)?(${ERROR_ACTION})|(${ERROR_ACTION}))\b`, "g");
|
|
25
|
+
const NEGATED_ERROR_PREFIX = /\b(?:never|rather\s+than|instead\s+of|as\s+opposed\s+to|except|other\s+than|(?:but\s+)?not|no)(?:\s+an?)?\s*$/;
|
|
26
|
+
const NEGATED_ERROR_ACTION_PREFIX = /\b(?:without|avoid(?:s|ed|ing)?|prevent(?:s|ed|ing)?|suppress(?:es|ed|ing)?)\s+(?:ever\s+)?(?:throw(?:s|n|ing)?|rais(?:e|es|ed|ing)|reject(?:s|ed|ing)?|us(?:e|es|ed|ing)|return(?:s|ed|ing)?|emit(?:s|ted|ting)?|produc(?:e|es|ed|ing))?(?:\s+an?)?\s*$/;
|
|
27
|
+
const NON_REQUEST_SOURCE_PREFIX = /\b(?:catch(?:es|ing)?|caught|handl(?:e|es|ed|ing)|observ(?:e|es|ed|ing)|receiv(?:e|es|ed|ing)|inspect(?:s|ed|ing)?|log(?:s|ged|ging)?|ignor(?:e|es|ed|ing)|swallow(?:s|ed|ing)?|convert(?:s|ed|ing)?|wrap(?:s|ped|ping)?|translat(?:e|es|ed|ing)|replac(?:e|es|ed|ing)|chang(?:e|es|ed|ing)|turn(?:s|ed|ing)?)\s+(?:an?)?\s*$/;
|
|
28
|
+
const NEGATED_ERROR_SUFFIX = /^\s+(?:(?:must|should|shall|can|could|may|might|will|would)\s+)?(?:not|never)\s+be\s+(?:thrown|raised|used|returned|emitted|produced)\b|^\s+is\s+(?:not\s+(?:allowed|required|expected|acceptable|equivalent)|forbidden|disallowed|prohibited)\b/;
|
|
29
|
+
const AFFIRMATIVE_ERROR_SUFFIX = /^\s+(?:(?:must|should|shall|can|could|may|might|will|would)\s+)?(?:be\s+)?(?:thrown|raised|used|returned|emitted|produced|required|expected)\b|^\s+is\s+(?:required|expected)\b/;
|
|
30
|
+
const TRANSFORM_TARGET_PREFIX = /\b(?:convert(?:s|ed|ing)?|wrap(?:s|ped|ping)?|translat(?:e|es|ed|ing)|map(?:s|ped|ping)?|replac(?:e|es|ed|ing)|chang(?:e|es|ed|ing)|turn(?:s|ed|ing)?)\b[^.;!?\n]{0,96}\b(?:into|to|as)(?:\s+an?)?\s*$/;
|
|
31
|
+
const RESULT_TARGET_PREFIX = /\b(?:result(?:s|ed|ing)?\s+in|resolve(?:s|d|ing)?\s+to)(?:\s+an?)?\s*$/;
|
|
32
|
+
const MAPPED_PARTITION_SUFFIX = /^\s*`?\s+(?:for|with)\s+(?:an?\s+)?(?:negative|fractional|zero|null|empty[- ]string|whitespace-only|non-finite|unsafe[- ]integer|missing|undefined|non-array|non-string|arrays?|plain[- ]objects?|primitives?)\b/;
|
|
33
|
+
const NEUTRAL_CONSTRUCTOR_SUFFIX = /^\s*`?\s*(?:from|reported\s+by|(?:is|was)\s+(?:caught|handled|logged|ignored|observed)|(?:is|was)\s+(?:not\s+allowed|forbidden|disallowed|prohibited))\b/;
|
|
34
|
+
const ERROR_MAPPING_PARTITIONS = Object.freeze([
|
|
35
|
+
["negative", /\bnegative\b/], ["fractional", /\bfractional\b/], ["zero", /\bzero\b/], ["null", /\bnull\b/],
|
|
36
|
+
["empty-string", /\bempty[- ]string\b/], ["whitespace-string", /\bwhitespace-only\b/],
|
|
37
|
+
["non-finite", /\bnon-finite\b/], ["unsafe-integer", /\bunsafe[- ]integer\b/],
|
|
38
|
+
["missing", /\b(?:missing|undefined)\b/], ["non-array", /\bnon-array\b/], ["non-string", /\bnon-string\b/],
|
|
39
|
+
["array", /\barrays?\b/], ["plain-object", /\bplain[- ]objects?\b/], ["primitive", /\bprimitives?\b/]
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function normalizedText(value) {
|
|
43
|
+
return String(value ?? "")
|
|
44
|
+
.normalize("NFD")
|
|
45
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
46
|
+
.replace(/\b(is|are|was|were|do|does|did|should|would|could|must|will|shall)n['’]t\b/gi, "$1 not")
|
|
47
|
+
.replace(/\bcan['’]t\b/gi, "can not")
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function affirmativeErrorConstructorMention(value, name) {
|
|
52
|
+
for (const match of value.matchAll(new RegExp(`\\b${name}\\b`, "g"))) {
|
|
53
|
+
const start = match.index ?? 0;
|
|
54
|
+
const end = start + match[0].length;
|
|
55
|
+
const clauseStart = Math.max(value.lastIndexOf(".", start - 1), value.lastIndexOf(";", start - 1), value.lastIndexOf("!", start - 1), value.lastIndexOf("?", start - 1), value.lastIndexOf("\n", start - 1));
|
|
56
|
+
const followingBoundary = value.slice(end).search(/[.;!?\n]/);
|
|
57
|
+
const prefix = value.slice(clauseStart + 1, start).trimEnd();
|
|
58
|
+
const suffix = value.slice(end, followingBoundary < 0 ? value.length : end + followingBoundary);
|
|
59
|
+
if (NEGATED_ERROR_PREFIX.test(prefix) || NEGATED_ERROR_ACTION_PREFIX.test(prefix)
|
|
60
|
+
|| NON_REQUEST_SOURCE_PREFIX.test(prefix) || NEGATED_ERROR_SUFFIX.test(suffix)) continue;
|
|
61
|
+
const conditional = prefix.match(/\b(?:when|if|after|once|while)\b[^,;]*$/i);
|
|
62
|
+
if (conditional && !new RegExp(`\\b(?:must|should|shall|will)\\s+${ERROR_ACTION}\\b`, "i").test(conditional[0])) continue;
|
|
63
|
+
let lastActionNegated = false;
|
|
64
|
+
let actionObserved = false;
|
|
65
|
+
for (const action of prefix.matchAll(ERROR_ACTION_MENTION)) {
|
|
66
|
+
actionObserved = true;
|
|
67
|
+
lastActionNegated = Boolean(action[1]);
|
|
68
|
+
}
|
|
69
|
+
if (actionObserved) {
|
|
70
|
+
if (lastActionNegated) continue;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (AFFIRMATIVE_ERROR_SUFFIX.test(suffix) || TRANSFORM_TARGET_PREFIX.test(prefix)
|
|
74
|
+
|| RESULT_TARGET_PREFIX.test(prefix)) return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function bareConstructorSegment(value, start, end) {
|
|
80
|
+
const before = value.slice(0, start);
|
|
81
|
+
const boundaries = [...before.matchAll(/(?:[;,.!?\n]|\b(?:and|but|whereas|or)\b)/g)];
|
|
82
|
+
const segmentStart = boundaries.at(-1)?.index + boundaries.at(-1)?.[0].length || 0;
|
|
83
|
+
const following = value.slice(end), boundary = following.search(/[;,.!?\n]|\b(?:and|but|whereas|or)\b/);
|
|
84
|
+
const prefix = value.slice(segmentStart, start), suffix = value.slice(end, boundary < 0 ? value.length : end + boundary);
|
|
85
|
+
return /^\s*`?\s*$/.test(prefix) ? { suffix } : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ellipticalOrAmbiguousMention(value, name) {
|
|
89
|
+
let ambiguous = false;
|
|
90
|
+
for (const match of value.matchAll(new RegExp(`\\b${name}\\b`, "g"))) {
|
|
91
|
+
const segment = bareConstructorSegment(value, match.index ?? 0, (match.index ?? 0) + match[0].length);
|
|
92
|
+
if (!segment || NEUTRAL_CONSTRUCTOR_SUFFIX.test(segment.suffix)) continue;
|
|
93
|
+
if (MAPPED_PARTITION_SUFFIX.test(segment.suffix)) return { affirmative: true, ambiguous: false };
|
|
94
|
+
ambiguous = true;
|
|
95
|
+
}
|
|
96
|
+
return { affirmative: false, ambiguous };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function requestedErrorClasses(text) {
|
|
100
|
+
const value = normalizedText(text);
|
|
101
|
+
const raw = String(text ?? ""), eligible = ERROR_CONSTRUCTORS.filter((name) => name !== "error" || /\bError\b/.test(raw));
|
|
102
|
+
return eligible.filter((name) => affirmativeErrorConstructorMention(value, name)
|
|
103
|
+
|| ellipticalOrAmbiguousMention(value, name).affirmative);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function hasAmbiguousErrorClassIntent(text) {
|
|
107
|
+
const value = normalizedText(text), raw = String(text ?? "");
|
|
108
|
+
return ERROR_CONSTRUCTORS.some((name) => (name !== "error" || /\bError\b/.test(raw))
|
|
109
|
+
&& !affirmativeErrorConstructorMention(value, name)
|
|
110
|
+
&& ellipticalOrAmbiguousMention(value, name).ambiguous);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function requestedErrorPartitionMapping(text, requestedErrors, requestedPartitions) {
|
|
114
|
+
const wanted = new Set(requestedPartitions), mappings = new Map();
|
|
115
|
+
const segments = normalizedText(text).split(/(?:[.;!?\n]+|,\s*|\s+\b(?:and|but|whereas)\b\s+)/).filter(Boolean);
|
|
116
|
+
for (const segment of segments) {
|
|
117
|
+
const partitions = ERROR_MAPPING_PARTITIONS.filter(([partition, pattern]) => wanted.has(partition) && pattern.test(segment)).map(([partition]) => partition);
|
|
118
|
+
const classes = requestedErrors.filter((name) => new RegExp(`\\b${name}\\b`).test(segment));
|
|
119
|
+
if (partitions.length === 0 && classes.length === 0) continue;
|
|
120
|
+
if (partitions.length !== 1 || classes.length !== 1
|
|
121
|
+
|| mappings.has(partitions[0]) && mappings.get(partitions[0]) !== classes[0]) return null;
|
|
122
|
+
mappings.set(partitions[0], classes[0]);
|
|
123
|
+
}
|
|
124
|
+
if (mappings.size !== wanted.size || requestedErrors.some((name) => ![...mappings.values()].includes(name))) return null;
|
|
125
|
+
return [...mappings].map(([partition, errorClass]) => ({ partition, errorClass }));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function assertionsProveErrorMapping(assertions, targets, mapping) {
|
|
129
|
+
return mapping.every((expected) => {
|
|
130
|
+
const relevant = assertions.filter((assertion) => assertion.partitions.has(expected.partition)
|
|
131
|
+
&& assertion.targets.some((target) => targets.includes(target)));
|
|
132
|
+
return relevant.length > 0 && relevant.every((assertion) => (
|
|
133
|
+
assertion.errorClasses.length === 1 && assertion.errorClasses[0] === expected.errorClass
|
|
134
|
+
));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function rejectionStatementErrorClass(text, requestedErrors) {
|
|
139
|
+
const match = text.match(/^\s*throw\s+(?:new\s+)?([a-z_$][a-z0-9_$]*)\s*\(/);
|
|
140
|
+
const errorClass = match?.[1]?.toLowerCase();
|
|
141
|
+
if (!errorClass || !ERROR_CONSTRUCTORS.includes(errorClass)) return null;
|
|
142
|
+
const open = match[0].lastIndexOf("(");
|
|
143
|
+
let depth = 0, close = -1;
|
|
144
|
+
for (let index = open; index < Math.min(text.length, open + 2_000); index += 1) {
|
|
145
|
+
if (text[index] === "(") depth += 1;
|
|
146
|
+
else if (text[index] === ")" && --depth === 0) { close = index + 1; break; }
|
|
147
|
+
}
|
|
148
|
+
if (close === -1) return null;
|
|
149
|
+
const argument = text.slice(open + 1, close - 1).trim();
|
|
150
|
+
if (argument && !/^__pi_(?:(?:empty|double_quote|whitespace)_string|string|node_assert_module|code_generation_module|module_loader_module|error_name_[a-z]+)_literal__$/i.test(argument)) return null;
|
|
151
|
+
if (!/^(?:[ \t]*;|[ \t]*(?:\}|$))/u.test(text.slice(close))) return null;
|
|
152
|
+
return requestedErrors.length === 0 || requestedErrors.includes(errorClass) ? errorClass : null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function errorMappingsProveContract(observed, mapping) {
|
|
156
|
+
return mapping.every((expected) => {
|
|
157
|
+
const matches = observed.filter((actual) => actual.partition === expected.partition);
|
|
158
|
+
return matches.length > 0 && matches.every((actual) => actual.errorClass === expected.errorClass);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -243,7 +243,7 @@ function rejectionAssertionHelpers(testText, assertionCarriers) {
|
|
|
243
243
|
for (const declaration of assertionHelperDeclarations(testText)) {
|
|
244
244
|
if (/\b(?:if|for|while|switch|try|catch|return|throw|class|function)\b|\.(?:skip|todo)\b/.test(declaration.body)) continue;
|
|
245
245
|
const parameters = [...declaration.parameters.matchAll(/[a-z_$][a-z0-9_$]*/gi)].map((match) => match[0].toLowerCase()).slice(0, 8);
|
|
246
|
-
for (const match of declaration.body.matchAll(/\b([a-z_$][a-z0-9_$]*)\.(
|
|
246
|
+
for (const match of declaration.body.matchAll(/\b([a-z_$][a-z0-9_$]*)\.(throws|rejects)\s*\(/gi)) {
|
|
247
247
|
if (!assertionCarriers.has(match[1].toLowerCase())) continue;
|
|
248
248
|
const open = declaration.body.indexOf("(", match.index);
|
|
249
249
|
const end = balancedEnd(declaration.body, open);
|
|
@@ -254,7 +254,7 @@ function rejectionAssertionHelpers(testText, assertionCarriers) {
|
|
|
254
254
|
const before = declaration.body.slice(0, match.index).replace(/^\s*\{?\s*(?:return\s+|await\s+)?/, "");
|
|
255
255
|
const after = declaration.body.slice(end).replace(/;?\s*\}?\s*$/, "");
|
|
256
256
|
if (errorClasses.length > 0 && directParameter && !before && !after) {
|
|
257
|
-
helpers.push({ ...declaration, errorClasses });
|
|
257
|
+
helpers.push({ ...declaration, errorClasses, mode: match[2].toLowerCase() });
|
|
258
258
|
break;
|
|
259
259
|
}
|
|
260
260
|
}
|
|
@@ -283,7 +283,7 @@ function callbackBodyRange(text, argument) {
|
|
|
283
283
|
return end === -1 || end > argument.end + 1 ? undefined : { start: bodyStart + 1, end: end - 1, braced: true };
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
export function executableRejectionAssertions(testText, callableNames) {
|
|
286
|
+
export function executableRejectionAssertions(testText, callableNames, modeHintsOnly = false) {
|
|
287
287
|
const assertions = [];
|
|
288
288
|
const carrierBindings = assertionCarrierBindings(testText);
|
|
289
289
|
const assertionCarriers = new Set((assertionEvidenceFileSupported(testText)
|
|
@@ -420,6 +420,7 @@ export function executableRejectionAssertions(testText, callableNames) {
|
|
|
420
420
|
const forOf = header.match(/^\s*(?:const|let)\s+([a-z_$][a-z0-9_$]*)\s+of\s+([\s\S]+?)\s*$/i);
|
|
421
421
|
const iterable = forOf && iterationLiteral(forOf[2], match.index);
|
|
422
422
|
if (iterable) iterationRanges.push({ ...iterationBody, ...iterable, kind: "for-of", variable: forOf[1] });
|
|
423
|
+
else if (modeHintsOnly && forOf) iterationRanges.push({ ...iterationBody, kind: "for-of", variable: forOf[1] });
|
|
423
424
|
else unsupportedControlRanges.push(body);
|
|
424
425
|
}
|
|
425
426
|
for (const match of testText.matchAll(/\.foreach\s*\(/g)) {
|
|
@@ -460,7 +461,7 @@ export function executableRejectionAssertions(testText, callableNames) {
|
|
|
460
461
|
const prefix = testText.slice(range.start, start);
|
|
461
462
|
return !new RegExp(`\\b(?:const|let|var|function|class)\\s+${escaped}\\b|\\b${escaped}\\b\\s*(?:\\+\\+|--|[+*/%&|^-]=|=(?!=|>))|(?:\\+\\+|--)\\s*\\b${escaped}\\b`, "i").test(prefix);
|
|
462
463
|
};
|
|
463
|
-
const record = (start, end, errorClasses, operation) => {
|
|
464
|
+
const record = (start, end, errorClasses, operation, mode) => {
|
|
464
465
|
if (end === -1) return;
|
|
465
466
|
if ([...declarationRanges, ...skippedRanges, ...conditionalRanges, ...unsupportedControlRanges]
|
|
466
467
|
.some((range) => start >= range.start && start < range.end)) return;
|
|
@@ -495,16 +496,17 @@ export function executableRejectionAssertions(testText, callableNames) {
|
|
|
495
496
|
if (targets.length === 0) return;
|
|
496
497
|
const partitions = evidencePartitionSignals(assertion);
|
|
497
498
|
for (const range of referencedIterations) {
|
|
498
|
-
for (const signal of literalArrayPartitionSignals(range.literal)) partitions.add(signal);
|
|
499
|
+
if (range.literal) for (const signal of literalArrayPartitionSignals(range.literal)) partitions.add(signal);
|
|
499
500
|
}
|
|
500
|
-
assertions.push({ targets, errorClasses, partitions });
|
|
501
|
+
assertions.push({ targets, errorClasses, partitions, mode });
|
|
501
502
|
};
|
|
502
503
|
for (const match of testText.matchAll(/\b([a-z_$][a-z0-9_$]*)\.(?:throws|rejects)\s*\(/gi)) {
|
|
503
504
|
if (!assertionCarriers.has(match[1].toLowerCase())) continue;
|
|
504
505
|
const open = testText.indexOf("(", match.index);
|
|
505
506
|
const end = balancedEnd(testText, open);
|
|
506
507
|
const argumentsList = end === -1 ? [] : topLevelArgumentRanges(testText, open, end);
|
|
507
|
-
record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text
|
|
508
|
+
record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text,
|
|
509
|
+
/\.rejects\s*\(/i.test(match[0]) ? "rejects" : "throws");
|
|
508
510
|
}
|
|
509
511
|
for (const helper of rejectionAssertionHelpers(testText, assertionCarriers)) {
|
|
510
512
|
for (const match of testText.matchAll(new RegExp(`\\b${escapeRegex(helper.name)}\\s*\\(`, "g"))) {
|
|
@@ -512,7 +514,7 @@ export function executableRejectionAssertions(testText, callableNames) {
|
|
|
512
514
|
const open = testText.indexOf("(", match.index);
|
|
513
515
|
const end = balancedEnd(testText, open);
|
|
514
516
|
const argumentsList = end === -1 ? [] : topLevelArgumentRanges(testText, open, end);
|
|
515
|
-
record(match.index, end, helper.errorClasses, argumentsList[0]?.text);
|
|
517
|
+
record(match.index, end, helper.errorClasses, argumentsList[0]?.text, helper.mode);
|
|
516
518
|
}
|
|
517
519
|
}
|
|
518
520
|
for (const match of testText.matchAll(/\bexpect\s*\(/g)) {
|
|
@@ -525,7 +527,8 @@ export function executableRejectionAssertions(testText, callableNames) {
|
|
|
525
527
|
const throwOpen = testText.indexOf("(", expectEnd + chain.index);
|
|
526
528
|
const end = balancedEnd(testText, throwOpen);
|
|
527
529
|
const argumentsList = topLevelArgumentRanges(testText, open, expectEnd);
|
|
528
|
-
record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text
|
|
530
|
+
record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text,
|
|
531
|
+
/\.rejects/i.test(chain[0]) ? "rejects" : "throws");
|
|
529
532
|
}
|
|
530
533
|
return assertions.slice(0, 64);
|
|
531
534
|
}
|