chore(lint): guard validation ownership and asynchronous assertions

This commit is contained in:
morluto committed 2026-10-25 07:57:07 +08:00
1 parent ed86f9c3bb
commit 76afdd35ae
69 files changed
+2384 -190

No files matched your search

+68 -4
View File
@@ -1,7 +1,6 @@
{ {
"$schema": "./node_modules/oxlint/configuration_schema.json", "$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn", "oxc"], "plugins": ["typescript", "import", "unicorn", "oxc", "vitest"],
"jsPlugins": [{ "name": "rea", "specifier": "./scripts/lint/index.mjs" }],
"rules": { "rules": {
"rea/no-buffer-deep-equality": "error", "rea/no-buffer-deep-equality": "error",
"rea/cancellation-cause": "error", "rea/cancellation-cause": "error",
@@ -29,7 +28,37 @@
"max-params": ["warn", 4], "max-params": ["warn", 4],
"no-var": "warn", "no-var": "warn",
"no-var-requires": "error", "no-var-requires": "error",
"typescript/no-import-type-side-effects": "error" "typescript/no-import-type-side-effects": "error",
"vitest/no-standalone-expect": "off",
"vitest/no-conditional-expect": "off",
"vitest/expect-expect": "off",
"vitest/no-disabled-tests": "off",
"vitest/require-mock-type-parameters": "off",
"vitest/require-to-throw-message": "off",
"import/no-duplicates": [
"warn",
{
"preferInline": false
}
],
"vitest/valid-expect": [
"warn",
{
"maxArgs": 2
}
],
"rea/no-evidence-json-preadmission": "error",
"rea/no-positional-tool-contract": "error",
"rea/no-fixture-root-cleanup": "error",
"typescript/no-floating-promises": "warn",
"typescript/no-misused-promises": "warn",
"typescript/return-await": ["warn", "error-handling-correctness-only"],
"typescript/switch-exhaustiveness-check": [
"warn",
{
"considerDefaultExhaustiveForUnions": false
}
]
}, },
"overrides": [ "overrides": [
{ {
@@ -42,6 +71,12 @@
"no-var-requires": "off" "no-var-requires": "off"
} }
}, },
{
"files": ["src/**/*.ts"],
"rules": {
"typescript/no-misused-promises": "error"
}
},
{ {
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"], "files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
"rules": { "rules": {
@@ -53,7 +88,8 @@
"skipBlankLines": true, "skipBlankLines": true,
"skipComments": true "skipComments": true
} }
] ],
"typescript/no-misused-promises": "warn"
} }
}, },
{ {
@@ -97,6 +133,25 @@
} }
] ]
} }
},
{
"files": ["tests/**/*.test.ts", "src/**/*.test.ts"],
"rules": {
"vitest/valid-expect": [
"warn",
{
"maxArgs": 2
}
],
"vitest/valid-expect-in-promise": "error",
"vitest/no-focused-tests": "error"
}
},
{
"files": ["src/**/*.test.ts"],
"rules": {
"typescript/no-floating-promises": "error"
}
} }
], ],
"env": { "env": {
@@ -115,5 +170,14 @@
".devin", ".devin",
".factory", ".factory",
"third_party" "third_party"
],
"options": {
"reportUnusedDisableDirectives": "error"
},
"jsPlugins": [
{
"name": "rea",
"specifier": "./scripts/lint/index.mjs"
}
] ]
} }
+25 -7
View File
@@ -159,13 +159,31 @@ cause when a directly checked caller `AbortSignal` is converted to an
follow indirect cancellation helpers, or prove that an explicit cause equals follow indirect cancellation helpers, or prove that an explicit cause equals
the caller's reason. Keep focused boundary and lifecycle tests for those facts. the caller's reason. Keep focused boundary and lifecycle tests for those facts.
Type-aware linting remains outside the routine gate: a full source, test, and `rea/no-evidence-json-preadmission` rejects direct generic JSON parses in
script scan exceeded the bounded process-family memory envelope, including `createEvidence` payload expressions; it does not analyze intermediate values.
with Go runtime limits. Future scoped evaluation can consider `rea/no-positional-tool-contract` rejects catalog indices used to select
`typescript/no-floating-promises`, `typescript/return-await` with operation names, schemas or types; display-only selection remains allowed.
`error-handling-correctness-only`, `typescript/no-misused-promises`, and `rea/no-fixture-root-cleanup` follows `const` fixture-root aliases into teardown
`typescript/switch-exhaustiveness-check`. None replaces behavioral checks for deletion; mutable bindings and mixed root arrays remain outside its scope.
source identity, omission versus unknown, or Evidence provenance.
Focused `.only` declarations, broken assertion promise chains, and unused disable
directives are errors. `vitest/valid-expect` warns (`maxArgs: 2`) because valid stored and
collected assertions can be awaited later. `import/no-duplicates` warns with
`preferInline: false`, preserving separate type imports.
Fast lint stays syntax-based. `npm run lint:typed` runs serially with pinned
`oxlint-tsgolint` 7.0.2005 and records native plus typed diagnostics in
`.cache/static-checks/typed-oxlint.json`. Production `src` treats
`no-misused-promises` as an error; tests/fixtures warn. `no-floating-promises` is
an error in `src/**/*.test.ts`; other scopes, return-await and exhaustiveness
findings warn pending triage.
`npm run knip:production` writes advisory candidates to
`.cache/static-checks/production-reachability.json`; execution/report errors fail.
Ordinary Knip remains. `check`, `check:pr`, and `check:ci:deep` run reachability
then typed lint sequentially after cached work. Typed lint builds the project
type graph and stays separate from fast feedback. Its default Go settings are
`GOMAXPROCS=2` and `GOMEMLIMIT=1024MiB`; caller-selected settings take precedence.
Routine iterations and rebases need focused regressions and relevant checks. Routine iterations and rebases need focused regressions and relevant checks.
Before handing off a PR, run `npm run check` and generated-document checks when Before handing off a PR, run `npm run check` and generated-document checks when
+35
View File
@@ -0,0 +1,35 @@
{
"$schema": "./node_modules/knip/schema.json",
"entry": [
"src/main.ts!",
"src/cli.ts!",
"src/cliOutput.ts!",
"src/cliBuiltinArguments.ts!",
"src/cli/cliResultOutput.ts!",
"src/mcpDoctor.ts!",
"src/composition/javascriptAnalysisWorkerProcess.ts!",
"src/composition/webBundleAnalysisWorkerProcess.ts!",
"src/sqlite/SqliteDatabaseWorker.ts!",
"src/evm/EvmoleInterfaceWorker.ts!",
"src/browser/history/NodeCaptureProcess.ts!",
"src/javascript/sourceMaps/SourceMapCodecProcess.ts!",
"scripts/rea.mjs!",
"scripts/rea-android-workspace-guardian.mjs!",
"scripts/electron-active-hook.cjs!",
"scripts/prepack.mjs!",
"scripts/prepare.mjs!",
"scripts/verify-windows-native-artifact.mjs!"
],
"project": ["src/**/*.ts!", "!src/**/*.test.ts", "!src/**/*.fixture.ts"],
"ignoreExportsUsedInFile": true,
"include": [
"files",
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
"duplicates"
]
}
+36 -1
View File
@@ -2,7 +2,7 @@ REA npm dependency inventory
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change. Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
License identifiers are upstream lockfile declarations, not a legal assessment. License identifiers are upstream lockfile declarations, not a legal assessment.
Runtime includes transitive and optional platform packages; development includes build, test and website tooling. Runtime includes transitive and optional platform packages; development includes build, test and website tooling.
Locked package entries: 956; runtime: 180. Locked package entries: 963; runtime: 180.
Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt. Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
@algolia/abtesting@1.25.0 | development | MIT @algolia/abtesting@1.25.0 | development | MIT
@@ -1100,6 +1100,36 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@oxfmt/binding-win32-x64-msvc/v/0.72.0 Package: https://www.npmjs.com/package/@oxfmt/binding-win32-x64-msvc/v/0.72.0
Archive: https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.72.0.tgz Archive: https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.72.0.tgz
Integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw== Integrity: sha512-uhooIN+DCzWX5uH7mQkSVCsODqsGluGJ4n3ch4+mITtWKpESMDjUJUSDBAVFuYQDwqY2McPDKdKJbM0oZVW/Qw==
@oxlint-tsgolint/darwin-arm64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/darwin-arm64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/darwin-arm64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz
Integrity: sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==
@oxlint-tsgolint/darwin-x64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/darwin-x64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/darwin-x64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz
Integrity: sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==
@oxlint-tsgolint/linux-arm64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/linux-arm64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/linux-arm64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz
Integrity: sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==
@oxlint-tsgolint/linux-x64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/linux-x64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/linux-x64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz
Integrity: sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==
@oxlint-tsgolint/win32-arm64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/win32-arm64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/win32-arm64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz
Integrity: sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==
@oxlint-tsgolint/win32-x64@7.0.2005 | development, optional | MIT
Lockfile path: node_modules/@oxlint-tsgolint/win32-x64
Package: https://www.npmjs.com/package/@oxlint-tsgolint/win32-x64/v/7.0.2005
Archive: https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz
Integrity: sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==
@oxlint/binding-android-arm-eabi@1.87.0 | development, optional | MIT @oxlint/binding-android-arm-eabi@1.87.0 | development, optional | MIT
Lockfile path: node_modules/@oxlint/binding-android-arm-eabi Lockfile path: node_modules/@oxlint/binding-android-arm-eabi
Package: https://www.npmjs.com/package/@oxlint/binding-android-arm-eabi/v/1.87.0 Package: https://www.npmjs.com/package/@oxlint/binding-android-arm-eabi/v/1.87.0
@@ -3540,6 +3570,11 @@ oxlint@1.87.0 | development | MIT
Package: https://www.npmjs.com/package/oxlint/v/1.87.0 Package: https://www.npmjs.com/package/oxlint/v/1.87.0
Archive: https://registry.npmjs.org/oxlint/-/oxlint-1.87.0.tgz Archive: https://registry.npmjs.org/oxlint/-/oxlint-1.87.0.tgz
Integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg== Integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==
oxlint-tsgolint@7.0.2005 | development | MIT
Lockfile path: node_modules/oxlint-tsgolint
Package: https://www.npmjs.com/package/oxlint-tsgolint/v/7.0.2005
Archive: https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz
Integrity: sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==
p-limit@2.3.0 | development | MIT p-limit@2.3.0 | development | MIT
Lockfile path: node_modules/p-limit Lockfile path: node_modules/p-limit
Package: https://www.npmjs.com/package/p-limit/v/2.3.0 Package: https://www.npmjs.com/package/p-limit/v/2.3.0
+103
View File
@@ -73,6 +73,7 @@
"lint-staged": "16.4.0", "lint-staged": "16.4.0",
"oxfmt": "0.72.0", "oxfmt": "0.72.0",
"oxlint": "1.87.0", "oxlint": "1.87.0",
"oxlint-tsgolint": "7.0.2005",
"release-please": "17.3.0", "release-please": "17.3.0",
"rolldown": "1.2.12", "rolldown": "1.2.12",
"tsrs": "0.18.0-ts7.1.0-dev.20260929", "tsrs": "0.18.0-ts7.1.0-dev.20260929",
@@ -3491,6 +3492,90 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
} }
}, },
"node_modules/@oxlint-tsgolint/darwin-arm64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz",
"integrity": "sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxlint-tsgolint/darwin-x64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz",
"integrity": "sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxlint-tsgolint/linux-arm64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz",
"integrity": "sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint-tsgolint/linux-x64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz",
"integrity": "sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint-tsgolint/win32-arm64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz",
"integrity": "sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxlint-tsgolint/win32-x64": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz",
"integrity": "sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxlint/binding-android-arm-eabi": { "node_modules/@oxlint/binding-android-arm-eabi": {
"version": "1.87.0", "version": "1.87.0",
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.87.0.tgz", "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.87.0.tgz",
@@ -10137,6 +10222,24 @@
} }
} }
}, },
"node_modules/oxlint-tsgolint": {
"version": "7.0.2005",
"resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz",
"integrity": "sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==",
"dev": true,
"license": "MIT",
"bin": {
"tsgolint": "bin/tsgolint.js"
},
"optionalDependencies": {
"@oxlint-tsgolint/darwin-arm64": "7.0.2005",
"@oxlint-tsgolint/darwin-x64": "7.0.2005",
"@oxlint-tsgolint/linux-arm64": "7.0.2005",
"@oxlint-tsgolint/linux-x64": "7.0.2005",
"@oxlint-tsgolint/win32-arm64": "7.0.2005",
"@oxlint-tsgolint/win32-x64": "7.0.2005"
}
},
"node_modules/p-limit": { "node_modules/p-limit": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+7 -4
View File
@@ -112,7 +112,7 @@
"prebuild": "npm run deps:check", "prebuild": "npm run deps:check",
"build": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills --force", "build": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills --force",
"precheck": "npm run deps:check", "precheck": "npm run deps:check",
"check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards", "check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards && npm run knip:production && npm run lint:typed",
"precheck:fast": "npm run deps:check", "precheck:fast": "npm run deps:check",
"check:fast": "node scripts/run-turbo.mjs run typecheck lint --output-logs=errors-only", "check:fast": "node scripts/run-turbo.mjs run typecheck lint --output-logs=errors-only",
"precheck:changed": "npm run deps:check", "precheck:changed": "npm run deps:check",
@@ -120,14 +120,14 @@
"precheck:test": "npm run deps:check", "precheck:test": "npm run deps:check",
"check:test": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run", "check:test": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run",
"precheck:pr": "npm run deps:check", "precheck:pr": "npm run deps:check",
"check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos", "check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos && npm run knip:production && npm run lint:typed",
"precheck:ci": "npm run deps:check", "precheck:ci": "npm run deps:check",
"precheck:ci:fast": "npm run deps:check", "precheck:ci:fast": "npm run deps:check",
"precheck:ci:deep": "npm run deps:check", "precheck:ci:deep": "npm run deps:check",
"check:ci": "npm run check:ci:fast && npm run check:ci:deep", "check:ci": "npm run check:ci:fast && npm run check:ci:deep",
"check:ci:fast": "node scripts/run-exclusive.mjs artifacts npm run check:ci:fast:tasks", "check:ci:fast": "node scripts/run-exclusive.mjs artifacts npm run check:ci:fast:tasks",
"check:ci:fast:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check", "check:ci:fast:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check",
"check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python", "check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python && npm run knip:production && npm run lint:typed",
"check:ci:deep:tasks": "node scripts/run-turbo.mjs run knip metadata:check jscpd scan:todos", "check:ci:deep:tasks": "node scripts/run-turbo.mjs run knip metadata:check jscpd scan:todos",
"check:ci:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check jscpd scan:todos", "check:ci:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check jscpd scan:todos",
"ci:static-context": "node scripts/ci/static-task-context.mjs", "ci:static-context": "node scripts/ci/static-task-context.mjs",
@@ -326,7 +326,9 @@
"docs:prepare": "node scripts/generate-package-metadata.mjs && node scripts/run-turbo.mjs run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema", "docs:prepare": "node scripts/generate-package-metadata.mjs && node scripts/run-turbo.mjs run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema",
"verify:web:qlog": "npm run build:cached && node scripts/verify-qlog-captures.mjs", "verify:web:qlog": "npm run build:cached && node scripts/verify-qlog-captures.mjs",
"licenses:generate": "node scripts/generate-third-party-inventory.mjs", "licenses:generate": "node scripts/generate-third-party-inventory.mjs",
"licenses:check": "node scripts/generate-third-party-inventory.mjs --check" "licenses:check": "node scripts/generate-third-party-inventory.mjs --check",
"lint:typed": "node scripts/ci/static-check-report.mjs typed-lint",
"knip:production": "node scripts/ci/production-reachability-report.mjs"
}, },
"lint-staged": { "lint-staged": {
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [ "*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [
@@ -404,6 +406,7 @@
"lint-staged": "16.4.0", "lint-staged": "16.4.0",
"oxfmt": "0.72.0", "oxfmt": "0.72.0",
"oxlint": "1.87.0", "oxlint": "1.87.0",
"oxlint-tsgolint": "7.0.2005",
"release-please": "17.3.0", "release-please": "17.3.0",
"rolldown": "1.2.12", "rolldown": "1.2.12",
"tsrs": "0.18.0-ts7.1.0-dev.20260929", "tsrs": "0.18.0-ts7.1.0-dev.20260929",
+1 -1
View File
@@ -108,7 +108,7 @@ function classifyPackage(base, head) {
continue; continue;
} }
if ( if (
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip$|jscpd$)/.test( /^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip(?::|$)|jscpd$)/.test(
script, script,
) )
) )
@@ -0,0 +1,33 @@
export interface ProductionReachabilityFinding {
readonly name: string;
readonly namespace?: string;
readonly kind?: string;
readonly specifier?: string;
readonly line?: number;
readonly col?: number;
readonly pos?: number;
}
export interface ProductionReachabilityIssue {
readonly file: string;
readonly owners?: readonly { readonly name: string }[];
readonly files?: readonly ProductionReachabilityFinding[];
readonly exports?: readonly ProductionReachabilityFinding[];
readonly nsExports?: readonly ProductionReachabilityFinding[];
readonly types?: readonly ProductionReachabilityFinding[];
readonly nsTypes?: readonly ProductionReachabilityFinding[];
readonly enumMembers?: readonly ProductionReachabilityFinding[];
readonly namespaceMembers?: readonly ProductionReachabilityFinding[];
readonly duplicates?: readonly (readonly ProductionReachabilityFinding[])[];
}
export interface ProductionReachabilityReport {
readonly issues: readonly ProductionReachabilityIssue[];
}
export function parseProductionReachabilityReport(
text: string,
): ProductionReachabilityReport;
export function recordProductionReachabilityReport(
root?: string,
): Promise<ProductionReachabilityReport>;
@@ -0,0 +1,86 @@
import { spawnSync } from "node:child_process";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";
const require = createRequire(import.meta.url);
const finding = z.object({
name: z.string(),
namespace: z.string().optional(),
kind: z.string().optional(),
specifier: z.string().optional(),
line: z.number().optional(),
col: z.number().optional(),
pos: z.number().optional(),
});
const issue = z.strictObject({
file: z.string(),
owners: z.array(z.object({ name: z.string() })).optional(),
files: z.array(finding).optional(),
exports: z.array(finding).optional(),
nsExports: z.array(finding).optional(),
types: z.array(finding).optional(),
nsTypes: z.array(finding).optional(),
enumMembers: z.array(finding).optional(),
namespaceMembers: z.array(finding).optional(),
duplicates: z.array(z.array(finding)).optional(),
});
const reportSchema = z.strictObject({ issues: z.array(issue) });
/** Admit the installed Knip JSON reporter's candidate records. */
export const parseProductionReachabilityReport = (text) =>
reportSchema.parse(JSON.parse(text));
/** Run production reachability without turning candidates into failures. */
export const recordProductionReachabilityReport = async (
root = process.cwd(),
) => {
const reportDirectory = join(root, ".cache", "static-checks");
const reportPath = join(reportDirectory, "production-reachability.json");
// Findings are advisory. Execution, configuration and report-admission failures
// still fail, and must never leave an older successful report at this path.
await mkdir(reportDirectory, { recursive: true });
await rm(reportPath, { force: true });
const result = spawnSync(
process.execPath,
[
join(dirname(require.resolve("knip")), "..", "bin", "knip.js"),
"--config",
"knip.production.json",
"--production",
"--reporter",
"json",
"--no-exit-code",
],
{ cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 },
);
if (result.stderr) process.stderr.write(result.stderr);
if (result.error !== undefined) throw result.error;
if (result.status !== 0) {
if (result.stdout) process.stderr.write(result.stdout);
throw new Error(
result.status === null
? `Production reachability analysis terminated by ${result.signal}`
: `Production reachability analysis failed with status ${result.status}`,
);
}
const report = parseProductionReachabilityReport(result.stdout);
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
console.log(
`Production reachability: ${report.issues.length} files with advisory candidates.`,
);
console.log(`Full report: ${reportPath}`);
console.log(
"Candidates require runtime, verifier and published-contract review before removal; ordinary Knip remains separate.",
);
return report;
};
if (
process.argv[1] !== undefined &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
)
await recordProductionReachabilityReport();
+35 -11
View File
@@ -48,7 +48,12 @@ const duplicateReport = z
}) })
.passthrough(); .passthrough();
const run = (packageName, entrypoint, arguments_) => { const run = (
packageName,
entrypoint,
arguments_,
environment = process.env,
) => {
const executable = join( const executable = join(
dirname(require.resolve(`${packageName}/package.json`)), dirname(require.resolve(`${packageName}/package.json`)),
entrypoint, entrypoint,
@@ -56,6 +61,7 @@ const run = (packageName, entrypoint, arguments_) => {
const result = spawnSync(process.execPath, [executable, ...arguments_], { const result = spawnSync(process.execPath, [executable, ...arguments_], {
encoding: "utf8", encoding: "utf8",
maxBuffer: 32 * 1024 * 1024, maxBuffer: 32 * 1024 * 1024,
env: environment,
}); });
if (result.stderr) process.stderr.write(result.stderr); if (result.stderr) process.stderr.write(result.stderr);
if (result.error !== undefined) throw result.error; if (result.error !== undefined) throw result.error;
@@ -66,14 +72,30 @@ const run = (packageName, entrypoint, arguments_) => {
try { try {
await mkdir(reportRoot, { recursive: true }); await mkdir(reportRoot, { recursive: true });
if (mode === "lint") { if (mode === "lint" || mode === "typed-lint") {
const reportPath = join(reportRoot, "oxlint.json"); const typed = mode === "typed-lint";
const reportPath = join(
reportRoot,
typed ? "typed-oxlint.json" : "oxlint.json",
);
await rm(reportPath, { force: true }); await rm(reportPath, { force: true });
const result = run("oxlint", "bin/oxlint", [ const result = run(
...extraArguments, "oxlint",
"--format", "bin/oxlint",
"json", [
]); ...(typed ? ["--type-aware", "--threads", "1"] : []),
...extraArguments,
"--format",
"json",
],
typed
? {
...process.env,
GOMAXPROCS: process.env.GOMAXPROCS ?? "2",
GOMEMLIMIT: process.env.GOMEMLIMIT ?? "1024MiB",
}
: process.env,
);
let report; let report;
try { try {
report = lintReport.parse(JSON.parse(result.stdout)); report = lintReport.parse(JSON.parse(result.stdout));
@@ -89,7 +111,7 @@ try {
({ severity }) => severity === "error", ({ severity }) => severity === "error",
); );
console.log( console.log(
`Lint: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`, `${typed ? "Typed lint" : "Lint"}: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`,
); );
for (const diagnostic of errors) { for (const diagnostic of errors) {
const location = diagnostic.labels[0]?.span; const location = diagnostic.labels[0]?.span;
@@ -106,7 +128,9 @@ try {
) )
.slice(0, 8)) .slice(0, 8))
console.log(` ${rule}: ${count} warnings`); console.log(` ${rule}: ${count} warnings`);
console.log(`Full diagnostics: ${reportPath}; npm run lint:details`); console.log(
`Full diagnostics: ${reportPath}${typed ? "" : "; npm run lint:details"}`,
);
process.exitCode = result.status; process.exitCode = result.status;
} else if (mode === "duplicates") { } else if (mode === "duplicates") {
const output = join(reportRoot, "jscpd"); const output = join(reportRoot, "jscpd");
@@ -143,7 +167,7 @@ try {
console.log(`Full diagnostics: ${reportPath}; npm run jscpd:details`); console.log(`Full diagnostics: ${reportPath}; npm run jscpd:details`);
process.exitCode = result.status; process.exitCode = result.status;
} else { } else {
throw new Error("Expected lint or duplicates"); throw new Error("Expected lint, typed-lint or duplicates");
} }
} catch (cause) { } catch (cause) {
console.error(cause instanceof Error ? cause.message : String(cause)); console.error(cause instanceof Error ? cause.message : String(cause));
+7
View File
@@ -1,10 +1,17 @@
import noBufferDeepEquality from "./no-buffer-deep-equality.mjs"; import noBufferDeepEquality from "./no-buffer-deep-equality.mjs";
import cancellationCause from "./cancellation-cause.mjs"; import cancellationCause from "./cancellation-cause.mjs";
import noEvidenceJsonPreadmission from "./rules/no-evidence-json-preadmission.mjs";
import noFixtureRootCleanup from "./rules/no-fixture-root-cleanup.mjs";
import noPositionalToolContract from "./rules/no-positional-tool-contract.mjs";
export default { export default {
meta: { name: "rea" }, meta: { name: "rea" },
rules: { rules: {
"no-buffer-deep-equality": noBufferDeepEquality, "no-buffer-deep-equality": noBufferDeepEquality,
"cancellation-cause": cancellationCause, "cancellation-cause": cancellationCause,
"no-evidence-json-preadmission": noEvidenceJsonPreadmission,
"no-fixture-root-cleanup": noFixtureRootCleanup,
"no-positional-tool-contract": noPositionalToolContract,
}, },
}; };
+4
View File
@@ -0,0 +1,4 @@
export declare const resolveImportSource: (
filename: string,
specifier: unknown,
) => string | undefined;
+20
View File
@@ -0,0 +1,20 @@
import { dirname, isAbsolute, resolve } from "node:path";
import { fileURLToPath } from "node:url";
/** Resolve local import spellings without assigning local meaning to bare or HTTP imports. */
export const resolveImportSource = (filename, specifier) => {
if (typeof specifier !== "string") return undefined;
if (isAbsolute(specifier)) return resolve(specifier);
if (specifier.startsWith("file:")) {
try {
const url = new URL(specifier);
if (url.search !== "" || url.hash !== "") return undefined;
return fileURLToPath(url);
} catch {
return undefined;
}
}
if (specifier.startsWith("./") || specifier.startsWith("../"))
return resolve(dirname(filename), specifier);
return undefined;
};
@@ -0,0 +1,4 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,137 @@
import { findBinding } from "../bindings.mjs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { resolveImportSource } from "./import-source.mjs";
const domain = fileURLToPath(new URL("../../../src/domain/", import.meta.url));
const payloadFields = new Set(["parameters", "result", "rawResult"]);
const propertyName = (node) => {
if (!node.computed && node.property?.type === "Identifier")
return node.property.name;
if (
node.property?.type === "Literal" &&
typeof node.property.value === "string"
)
return node.property.value;
return undefined;
};
const fieldName = (node) => {
if (node.type !== "Property") return undefined;
if (!node.computed && node.key.type === "Identifier") return node.key.name;
if (node.key.type === "Literal" && typeof node.key.value === "string")
return node.key.value;
return undefined;
};
const importedBinding = (sourceCode, node) => {
if (node.type !== "Identifier") return undefined;
const variable = findBinding(sourceCode, node);
if (!variable || variable.defs.length !== 1) return undefined;
const definition = variable.defs[0];
return definition.type === "ImportBinding" ? definition : undefined;
};
const isImported = (context, node, module, names) => {
const member = node.type === "MemberExpression" && !node.optional;
const binding = importedBinding(
context.sourceCode,
member ? node.object : node,
);
if (!binding || binding.parent?.type !== "ImportDeclaration") return false;
const declaration = binding.parent;
if (declaration.importKind === "type" || binding.node.importKind === "type")
return false;
const specifier = declaration.source.value;
const target = resolveImportSource(
context.physicalFilename ?? context.filename,
specifier,
);
if (
target !== resolve(domain, `${module}.js`) &&
target !== resolve(domain, `${module}.ts`)
)
return false;
if (member)
return (
binding.node.type === "ImportNamespaceSpecifier" &&
names.has(propertyName(node))
);
if (binding.node.type !== "ImportSpecifier") return false;
return names.has(binding.node.imported.name ?? binding.node.imported.value);
};
const evidenceCreator = new Set(["createEvidence"]);
const genericJsonSchemas = new Set(["jsonValueSchema", "jsonObjectSchema"]);
export default {
meta: {
type: "suggestion",
docs: {
description:
"Leave ordinary Evidence JSON payload admission with createEvidence.",
},
schema: [],
messages: {
redundantJsonAdmission:
"Pass the {{field}} payload directly to createEvidence; it owns generic JSON admission. Keep semantic validation at its own boundary.",
},
},
create(context) {
return {
CallExpression(node) {
if (
node.optional ||
!isImported(context, node.callee, "evidence", evidenceCreator)
)
return;
const observation = node.arguments[2];
if (observation?.type !== "ObjectExpression") return;
for (const [index, property] of observation.properties.entries()) {
const field = fieldName(property);
if (
!payloadFields.has(field) ||
property.kind !== "init" ||
property.method
)
continue;
// A later spread or unknown key may replace this field before admission.
if (
observation.properties
.slice(index + 1)
.some(
(later) =>
later.type !== "Property" ||
fieldName(later) === undefined ||
fieldName(later) === field,
)
)
continue;
const parsed = property.value;
if (
parsed.type !== "CallExpression" ||
parsed.optional ||
parsed.arguments.length !== 1
)
continue;
const parse = parsed.callee;
if (
parse.type !== "MemberExpression" ||
parse.optional ||
propertyName(parse) !== "parse"
)
continue;
if (
isImported(context, parse.object, "jsonValue", genericJsonSchemas)
)
context.report({
node: parsed,
messageId: "redundantJsonAdmission",
data: { field },
});
}
},
};
},
};
@@ -0,0 +1,3 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,149 @@
import { findBinding } from "../bindings.mjs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { resolveImportSource } from "./import-source.mjs";
const root = fileURLToPath(new URL("../../../", import.meta.url));
const fixtureModule = resolve(root, "tests/fixtures/temporaryDirectory");
const workspaceModule = resolve(
root,
"tests/support/workspace/workspaceFixture",
);
const hooks = new Set(["afterEach", "afterAll", "onTestFinished"]);
const unwrap = (node) => {
while (
node &&
[
"AwaitExpression",
"TSAsExpression",
"TSNonNullExpression",
"ChainExpression",
].includes(node.type)
)
node = node.argument ?? node.expression;
return node;
};
const isFunction = (node) =>
node &&
[
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
].includes(node.type);
export default {
meta: {
type: "problem",
docs: {
description: "Let createTestTempDirectory own removal of its root.",
},
schema: [],
messages: {
duplicate:
"createTestTempDirectory already owns root cleanup; remove the duplicate teardown deletion, retaining cleanup of other resources.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const filename = context.physicalFilename ?? context.filename;
const calls = [];
const variable = (node) => findBinding(sourceCode, node);
const imported = (node) => {
const member = node?.type === "MemberExpression" ? node : undefined;
const binding = member ? member.object : node;
if (binding?.type !== "Identifier") return undefined;
const definition = variable(binding)?.defs[0];
if (
definition?.type !== "ImportBinding" ||
(member
? definition.node.type !== "ImportNamespaceSpecifier"
: definition.node.type !== "ImportSpecifier")
)
return undefined;
const declaration = definition.parent ?? definition.node.parent;
const specifier = declaration.source?.value;
if (typeof specifier !== "string") return undefined;
return {
name: member
? member.computed
? member.property.value
: member.property.name
: (definition.node.imported.name ?? definition.node.imported.value),
module:
resolveImportSource(filename, specifier)?.replace(
/\.(?:[cm]?[jt]s)$/u,
"",
) ?? specifier,
};
};
const fixtureRoot = (raw, seen = new Set()) => {
const node = unwrap(raw);
if (node?.type === "CallExpression") {
const identity = imported(node.callee);
return (
identity?.module === fixtureModule &&
identity.name === "createTestTempDirectory"
);
}
if (node?.type !== "Identifier") return false;
const binding = variable(node);
if (!binding || seen.has(binding)) return false;
seen.add(binding);
const definition = binding.defs[0];
// Mutable ownership and mixed root registries require flow analysis, not
// a guess based on the variable name or one assignment.
return (
definition?.type === "Variable" &&
definition.parent.kind === "const" &&
definition.node.id.type === "Identifier" &&
fixtureRoot(definition.node.init, seen)
);
};
const callback = (raw) => {
const node = unwrap(raw);
if (isFunction(node)) return node;
if (node?.type !== "Identifier") return undefined;
const definition = variable(node)?.defs[0];
if (definition?.type === "FunctionName") return definition.node;
if (
definition?.type === "Variable" &&
definition.parent.kind === "const"
) {
const value = unwrap(definition.node.init);
if (isFunction(value)) return value;
}
return undefined;
};
return {
CallExpression(node) {
calls.push(node);
},
"Program:exit"() {
const teardowns = new Set();
for (const call of calls) {
const identity = imported(call.callee);
if (identity?.module === "vitest" && hooks.has(identity.name)) {
const body = callback(call.arguments[0]);
if (body) teardowns.add(body);
}
}
for (const call of calls) {
const identity = imported(call.callee);
if (!identity) continue;
const removesRoot =
(identity.module === "node:fs/promises" &&
["rm", "rmdir"].includes(identity.name)) ||
(identity.module === "node:fs" &&
["rm", "rmSync", "rmdir", "rmdirSync"].includes(identity.name)) ||
(identity.module === workspaceModule &&
identity.name === "removeTestWorkspace");
if (!removesRoot || !fixtureRoot(call.arguments[0])) continue;
let parent = call.parent;
while (parent && !isFunction(parent)) parent = parent.parent;
if (teardowns.has(parent))
context.report({ node: call, messageId: "duplicate" });
}
},
};
},
};
@@ -0,0 +1,3 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,218 @@
import { findBinding } from "../bindings.mjs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { collectToolCatalogSources } from "./tool-catalog-sources.mjs";
import { resolveImportSource } from "./import-source.mjs";
const root = fileURLToPath(new URL("../../../", import.meta.url));
const catalogs = collectToolCatalogSources(
resolve(root, "src/contracts/toolContracts.ts"),
);
const unwrap = (node) => {
while (
node &&
[
"TSAsExpression",
"TSSatisfiesExpression",
"TSNonNullExpression",
"ChainExpression",
].includes(node.type)
)
node = node.expression;
return node;
};
const numeric = (node) =>
(node?.type === "Literal" &&
(typeof node.value === "number" ||
(typeof node.value === "string" &&
/^(?:0|[1-9][0-9]*)$/u.test(node.value)))) ||
(node?.type === "UnaryExpression" &&
["+", "-"].includes(node.operator) &&
node.argument.type === "Literal" &&
typeof node.argument.value === "number");
export default {
meta: {
type: "problem",
docs: {
description:
"Select canonical tool contracts by name, never catalog position.",
},
schema: [],
messages: {
positional:
"Select a named tool contract; catalog ordering must not select an operation or schema.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const filename = context.physicalFilename ?? context.filename;
const variable = (node) => findBinding(sourceCode, node);
const imported = (node) => {
if (node?.type !== "Identifier") return undefined;
const definition = variable(node)?.defs[0];
if (definition?.type !== "ImportBinding") return undefined;
const declaration = definition.parent ?? definition.node.parent;
const specifier = declaration.source?.value;
const path = resolveImportSource(filename, specifier)?.replace(
/\.(?:[cm]?[jt]s)$/u,
"",
);
const expected = catalogs.get(path);
if (expected === undefined) return undefined;
return { definition, expected };
};
const isCatalog = (raw, seen = new Set()) => {
const node = unwrap(raw);
if (!node) return false;
if (node.type === "Identifier") {
const binding = variable(node);
if (!binding || seen.has(binding)) return false;
seen.add(binding);
const identity = imported(node);
if (identity) {
const specifier = identity.definition.node;
return (
specifier.type === "ImportSpecifier" &&
identity.expected.has(
specifier.imported.name ?? specifier.imported.value,
)
);
}
const definition = binding.defs[0];
return (
definition?.type === "Variable" &&
definition.parent.kind === "const" &&
definition.node.id.type === "Identifier" &&
isCatalog(definition.node.init, seen)
);
}
if (node.type === "MemberExpression" || node.type === "TSQualifiedName") {
const object =
node.type === "MemberExpression" ? node.object : node.left;
const identity = imported(object);
if (identity?.definition.node.type !== "ImportNamespaceSpecifier")
return false;
const property =
node.type === "MemberExpression" ? node.property : node.right;
const name = node.computed ? property.value : property.name;
return identity.expected.has(name);
}
return false;
};
const selectedContract = (raw, seen = new Set()) => {
const node = unwrap(raw);
if (!node) return undefined;
if (
node.type === "MemberExpression" &&
node.computed &&
numeric(node.property) &&
isCatalog(node.object)
)
return node;
if (node.type === "CallExpression") {
const callee = unwrap(node.callee);
if (
callee?.type === "MemberExpression" &&
(callee.computed ? callee.property.value : callee.property.name) ===
"at" &&
numeric(node.arguments[0]) &&
isCatalog(callee.object)
)
return node;
}
if (node.type !== "Identifier") return undefined;
const binding = variable(node);
if (!binding || seen.has(binding)) return undefined;
seen.add(binding);
const definition = binding.defs[0];
if (definition?.type !== "Variable" || definition.parent.kind !== "const")
return undefined;
if (definition.node.id.type === "Identifier")
return selectedContract(definition.node.init, seen);
if (
definition.node.id.type === "ArrayPattern" &&
definition.node.id.elements.some(
(element) =>
element?.type === "Identifier" && element.name === node.name,
) &&
isCatalog(definition.node.init)
)
return definition.node;
return undefined;
};
const bindingFields = new Set(["name", "inputSchema", "outputSchema"]);
const isRegistrationOptions = (node) => {
if (node?.type !== "Identifier") return false;
const definition = variable(node)?.defs[0];
if (
definition?.type !== "ImportBinding" ||
definition.node.type !== "ImportSpecifier"
)
return false;
const declaration = definition.parent ?? definition.node.parent;
const specifier = declaration.source?.value;
const path = resolveImportSource(filename, specifier)?.replace(
/\.(?:[cm]?[jt]s)$/u,
"",
);
const name =
definition.node.imported.name ?? definition.node.imported.value;
return (
path === resolve(root, "src/server/toolRegistrationOptions") &&
[
"toolRegistrationOptions",
"applicationToolRegistrationOptions",
].includes(name)
);
};
const reported = new Set();
const reportSelection = (raw) => {
const node = selectedContract(raw);
if (node && !reported.has(node)) {
reported.add(node);
context.report({ node, messageId: "positional" });
}
};
return {
MemberExpression(node) {
const name = node.computed ? node.property.value : node.property.name;
const parent = node.parent;
const writesProperty =
(parent?.type === "AssignmentExpression" && parent.left === node) ||
parent?.type === "UpdateExpression" ||
(parent?.type === "UnaryExpression" && parent.operator === "delete");
if (bindingFields.has(name) && !writesProperty)
reportSelection(node.object);
},
CallExpression(node) {
if (isRegistrationOptions(unwrap(node.callee)))
reportSelection(node.arguments[0]);
},
VariableDeclarator(node) {
if (
node.id.type === "ObjectPattern" &&
node.id.properties.some(
(property) =>
property.type === "Property" &&
bindingFields.has(
property.computed ? property.key.value : property.key.name,
),
)
)
reportSelection(node.init);
},
TSIndexedAccessType(node) {
if (
node.objectType.type === "TSTypeQuery" &&
node.indexType.type === "TSLiteralType" &&
numeric(node.indexType.literal) &&
isCatalog(node.objectType.exprName)
)
context.report({ node, messageId: "positional" });
},
};
},
};
@@ -0,0 +1,4 @@
export declare const collectToolCatalogSources: (
entryPath: string,
readSource?: (path: string) => string,
) => ReadonlyMap<string, ReadonlySet<string>>;
+118
View File
@@ -0,0 +1,118 @@
import { parse } from "@babel/parser";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
const withoutExtension = (path) => path.replace(/\.(?:[cm]?[jt]s)$/u, "");
const arrayExpression = (raw) => {
let node = raw;
while (
node &&
["TSAsExpression", "TSSatisfiesExpression"].includes(node.type)
)
node = node.expression;
return node;
};
/** Read only source composition, never execute the runtime contract catalog. */
export const collectToolCatalogSources = (
entryPath,
readSource = (path) => readFileSync(path, "utf8"),
) => {
const catalogs = new Map();
const modules = new Map();
const active = new Set();
const complete = new Set();
const moduleFacts = (path) => {
let facts = modules.get(path);
if (facts) return facts;
// Read/parse errors intentionally escape: a broken inventory cannot disable
// the rule by presenting an empty set of catalog bindings.
const ast = parse(readSource(path), {
sourceType: "module",
plugins: ["typescript"],
});
const imports = new Map();
const exports = new Map();
const locals = new Map();
for (const statement of ast.program.body) {
if (statement.type === "ImportDeclaration") {
for (const specifier of statement.specifiers) {
if (specifier.type === "ImportSpecifier")
imports.set(specifier.local.name, {
source: statement.source.value,
name: specifier.imported.name ?? specifier.imported.value,
});
}
}
const declarations =
statement.type === "ExportNamedDeclaration"
? statement.declaration
: statement;
if (declarations?.type === "VariableDeclaration")
for (const declaration of declarations.declarations)
if (declaration.id.type === "Identifier") {
locals.set(declaration.id.name, declaration.init);
if (statement.type === "ExportNamedDeclaration")
exports.set(declaration.id.name, declaration.init);
}
}
facts = { imports, exports, locals };
modules.set(path, facts);
return facts;
};
const visit = (path, name) => {
const key = JSON.stringify([path, name]);
if (complete.has(key)) return;
if (active.has(key))
throw new Error(`Cyclic tool catalog composition: ${path}#${name}`);
active.add(key);
const facts = moduleFacts(path);
const array = arrayExpression(facts.exports.get(name));
if (array?.type !== "ArrayExpression")
throw new Error(`Expected exported tool catalog array: ${path}#${name}`);
const source = withoutExtension(path);
const names = catalogs.get(source) ?? new Set();
names.add(name);
catalogs.set(source, names);
const composition = (raw, localPath = new Set()) => {
const expression = arrayExpression(raw);
if (expression?.type === "ArrayExpression") {
for (const element of expression.elements)
if (element?.type === "SpreadElement")
composition(element.argument, localPath);
return;
}
// Calls such as HOPPER_MEMORY_TOOL_DEFINITIONS.map(official) construct
// entries locally; they are not imported catalog arrays. Do not execute
// or infer catalog ownership from arbitrary computed expressions.
if (expression?.type !== "Identifier") return;
const binding = facts.imports.get(expression.name);
if (binding) {
if (!binding.source.startsWith("."))
throw new Error(`Expected local catalog module: ${path}#${name}`);
visit(
`${withoutExtension(resolve(dirname(path), binding.source))}.ts`,
binding.name,
);
return;
}
if (localPath.has(expression.name))
throw new Error(
`Cyclic local catalog composition: ${path}#${expression.name}`,
);
if (!facts.locals.has(expression.name))
throw new Error(
`Unresolved local catalog spread: ${path}#${expression.name}`,
);
composition(
facts.locals.get(expression.name),
new Set([...localPath, expression.name]),
);
};
composition(array);
active.delete(key);
complete.add(key);
};
visit(entryPath, "TOOL_CONTRACTS");
return catalogs;
};
@@ -3,7 +3,6 @@ import { describe, expect, it } from "vitest";
import { JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE } from "../contracts/javascript/javascriptRuntimeReconciliationExample.js"; import { JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE } from "../contracts/javascript/javascriptRuntimeReconciliationExample.js";
import { createEvidence } from "../domain/evidence.js"; import { createEvidence } from "../domain/evidence.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js"; import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { javascriptApplicationAnalysisResultSchema } from "../domain/javascript/javascriptApplicationAnalysis.js"; import { javascriptApplicationAnalysisResultSchema } from "../domain/javascript/javascriptApplicationAnalysis.js";
import { artifactEvidence } from "../domain/javascript/javascriptApplicationGraph.fixture.js"; import { artifactEvidence } from "../domain/javascript/javascriptApplicationGraph.fixture.js";
import { import {
@@ -108,7 +107,7 @@ const semanticApplication = (
predicateType: example.predicate_type, predicateType: example.predicate_type,
operation: example.operation, operation: example.operation,
parameters: example.parameters, parameters: example.parameters,
result: jsonValueSchema.parse({ ...base, semantic_graph: graph }), result: { ...base, semantic_graph: graph },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -33,7 +33,6 @@ import {
createJavaScriptApplicationGraph, createJavaScriptApplicationGraph,
createJavaScriptApplicationNode, createJavaScriptApplicationNode,
} from "../domain/javascript/javascriptApplicationGraph.js"; } from "../domain/javascript/javascriptApplicationGraph.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { MANAGED_WORKFLOW_PROVIDER } from "../domain/investigationProviders.js"; import { MANAGED_WORKFLOW_PROVIDER } from "../domain/investigationProviders.js";
import { createProcessCaptureEvidence } from "./process/ProcessEvidence.js"; import { createProcessCaptureEvidence } from "./process/ProcessEvidence.js";
@@ -90,7 +89,7 @@ const applicationWithUnknowns = (
predicateType: "rea.managed-application-graph", predicateType: "rea.managed-application-graph",
operation: "project_managed_application_graph", operation: "project_managed_application_graph",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
projection_id: `magp_${"a".repeat(64)}`, projection_id: `magp_${"a".repeat(64)}`,
root_artifact_sha256: APPLICATION_GRAPH_DIGESTS.asar, root_artifact_sha256: APPLICATION_GRAPH_DIGESTS.asar,
source_evidence: { source_evidence: {
@@ -113,7 +112,7 @@ const applicationWithUnknowns = (
coverage: { status: "complete-within-inputs" }, coverage: { status: "complete-within-inputs" },
evidence_links: [source.evidence_id], evidence_links: [source.evidence_id],
limitations: [], limitations: [],
}), },
confidence: "inferred", confidence: "inferred",
authority: "analyst-inference", authority: "analyst-inference",
evidenceLinks: [source.evidence_id], evidenceLinks: [source.evidence_id],
@@ -212,7 +211,7 @@ describe("reconstruction obligation candidates", () => {
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...rest, ...rest,
manifest, manifest,
exit: { exit: {
@@ -224,7 +223,7 @@ describe("reconstruction obligation candidates", () => {
], ],
}, },
}, },
}), },
}); });
const generated = deriveCandidates(createEvidenceBundle([evidence]), []); const generated = deriveCandidates(createEvidenceBundle([evidence]), []);
@@ -308,7 +307,7 @@ it("keeps each semantic candidate bound to its own file and authority context",
predicateType: example.predicate_type, predicateType: example.predicate_type,
operation: example.operation, operation: example.operation,
parameters: example.parameters, parameters: example.parameters,
result: jsonValueSchema.parse({ ...base, semantic_graph: semanticGraph }), result: { ...base, semantic_graph: semanticGraph },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
evidenceLinks: sourceRecords.map(({ evidence_id }) => evidence_id), evidenceLinks: sourceRecords.map(({ evidence_id }) => evidence_id),
@@ -415,7 +414,7 @@ it("falls back to a kind-based title when a semantic node label is blank", () =>
predicateType: example.predicate_type, predicateType: example.predicate_type,
operation: example.operation, operation: example.operation,
parameters: example.parameters, parameters: example.parameters,
result: jsonValueSchema.parse({ ...base, semantic_graph: semanticGraph }), result: { ...base, semantic_graph: semanticGraph },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
evidenceLinks: [source.evidence_id], evidenceLinks: [source.evidence_id],
@@ -9,7 +9,6 @@ import { compareArtifacts } from "../../domain/artifactComparison.js";
import { parseArtifactInventoryEvidence } from "../../domain/artifactInventoryEvidence.js"; import { parseArtifactInventoryEvidence } from "../../domain/artifactInventoryEvidence.js";
import { projectAppleInventory } from "../../domain/apple/appleApplication.js"; import { projectAppleInventory } from "../../domain/apple/appleApplication.js";
import { createEvidence } from "../../domain/evidence.js"; import { createEvidence } from "../../domain/evidence.js";
import { jsonValueSchema } from "../../domain/jsonValue.js";
import { thinMach } from "../../domain/binaryTarget.fixture.js"; import { thinMach } from "../../domain/binaryTarget.fixture.js";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
@@ -32,7 +31,7 @@ const observeArchive = async (order: readonly string[]) => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventory), result: inventory,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -128,7 +127,7 @@ describe("content identity and occurrence facts", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventory), result: inventory,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -19,7 +19,6 @@ import {
import { readPyzArchive } from "./PyzArchive.js"; import { readPyzArchive } from "./PyzArchive.js";
import { marshalCode } from "../../python/PythonPyc.fixture.js"; import { marshalCode } from "../../python/PythonPyc.fixture.js";
import { createEvidence } from "../../domain/evidence.js"; import { createEvidence } from "../../domain/evidence.js";
import { jsonValueSchema } from "../../domain/jsonValue.js";
import { parseArtifactInventoryEvidence } from "../../domain/artifactInventoryEvidence.js"; import { parseArtifactInventoryEvidence } from "../../domain/artifactInventoryEvidence.js";
import { import {
ArtifactReaderFailure, ArtifactReaderFailure,
@@ -365,7 +364,7 @@ it.each([true, false])(
parameters: { page: index }, parameters: { page: index },
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
result: jsonValueSchema.parse({ result: {
...inventory, ...inventory,
nodes: index === 0 ? inventory.nodes : [], nodes: index === 0 ? inventory.nodes : [],
edges: index === 0 ? inventory.edges : [], edges: index === 0 ? inventory.edges : [],
@@ -378,7 +377,7 @@ it.each([true, false])(
row, row,
] ]
: [row], : [row],
}), },
}, },
), ),
); );
@@ -373,7 +373,6 @@ const emitDeepLink = (
); );
/** Read the current Electron windows, metrics, and hook snapshot. */ /** Read the current Electron windows, metrics, and hook snapshot. */
// oxlint-disable-next-line max-lines-per-function -- one state read must share one capture boundary.
export const readApplicationState = async ( export const readApplicationState = async (
application: ElectronApplication, application: ElectronApplication,
): Promise<{ ): Promise<{
+2 -3
View File
@@ -10,7 +10,6 @@ import { AnalysisInputError } from "../analysisErrorCore.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js"; import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { digestCanonicalValue } from "../canonicalDigest.js"; import { digestCanonicalValue } from "../canonicalDigest.js";
import { createEvidence, type Evidence } from "../evidence.js"; import { createEvidence, type Evidence } from "../evidence.js";
import { jsonValueSchema } from "../jsonValue.js";
type EntryKind = "file" | "directory" | "symlink" | "slice"; type EntryKind = "file" | "directory" | "symlink" | "slice";
type RootFormat = "ipa" | "zip" | "dmg" | "directory" | "asar"; type RootFormat = "ipa" | "zip" | "dmg" | "directory" | "asar";
@@ -136,7 +135,7 @@ const inventoryEvidence = (
parameters: {}, parameters: {},
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
result: jsonValueSchema.parse({ result: {
manifest: { manifest: {
manifest_id: `agm_${digestCanonicalValue( manifest_id: `agm_${digestCanonicalValue(
{ root_artifact_id: rootId, graph_sha256: graphSha256 }, { root_artifact_id: rootId, graph_sha256: graphSha256 },
@@ -157,7 +156,7 @@ const inventoryEvidence = (
integrity_contradictions: [], integrity_contradictions: [],
coverage: { scope: "container-entries", status: "complete" }, coverage: { scope: "container-entries", status: "complete" },
limitations: [], limitations: [],
}), },
}, },
); );
}; };
+3 -4
View File
@@ -14,7 +14,6 @@ import {
parseArtifactInspectionInventoryResult, parseArtifactInspectionInventoryResult,
} from "./artifactInventoryEvidence.js"; } from "./artifactInventoryEvidence.js";
import { createEvidence } from "./evidence.js"; import { createEvidence } from "./evidence.js";
import { jsonValueSchema } from "./jsonValue.js";
const inventoryEvidence = ARTIFACT_COMPARISON_EXAMPLE.left; const inventoryEvidence = ARTIFACT_COMPARISON_EXAMPLE.left;
const target = inventoryEvidence.subject; const target = inventoryEvidence.subject;
@@ -31,7 +30,7 @@ const inspectionEvidence = (result: unknown, subject = target) =>
{ {
operation: "inspect_artifact", operation: "inspect_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(result), result,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -152,12 +151,12 @@ it("keeps a commitment failure discovered across individually incomplete pages a
parameters: {}, parameters: {},
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
result: jsonValueSchema.parse({ result: {
...complete, ...complete,
manifest, manifest,
nodes: includeNodes ? complete.nodes : [], nodes: includeNodes ? complete.nodes : [],
occurrences: includeNodes ? [] : complete.occurrences, occurrences: includeNodes ? [] : complete.occurrences,
}), },
}, },
); );
const nodes = page(true); const nodes = page(true);
+6 -8
View File
@@ -7,7 +7,7 @@ import {
} from "./changedBehavior.js"; } from "./changedBehavior.js";
import { createEvidence, type Evidence } from "./evidence.js"; import { createEvidence, type Evidence } from "./evidence.js";
import { functionComparisonResultSchema } from "./functionComparisonSchemas.js"; import { functionComparisonResultSchema } from "./functionComparisonSchemas.js";
import { jsonValueSchema, type JsonValue } from "./jsonValue.js"; import type { JsonValue } from "./jsonValue.js";
const source = (digit: string): Evidence => const source = (digit: string): Evidence =>
createEvidence( createEvidence(
@@ -350,13 +350,11 @@ describe("changed-behavior input and Evidence validation", () => {
predicateType: base.predicate_type, predicateType: base.predicate_type,
operation: base.operation, operation: base.operation,
parameters: base.parameters, parameters: base.parameters,
result: jsonValueSchema.parse( result: functionComparisonResultSchema.parse({
functionComparisonResultSchema.parse({ ...result,
...result, dimensions,
dimensions, changes: dimensions.filter(({ status }) => status !== "unchanged"),
changes: dimensions.filter(({ status }) => status !== "unchanged"), }),
}),
),
confidence: base.confidence, confidence: base.confidence,
authority: base.authority, authority: base.authority,
evidenceLinks: base.evidence_links, evidenceLinks: base.evidence_links,
+1 -1
View File
@@ -664,7 +664,7 @@ describe("function comparison ordering controls", () => {
parameters: enhancedInputSchemas.analyze_function.parse({ parameters: enhancedInputSchemas.analyze_function.parse({
procedure: "dispatch", procedure: "dispatch",
}), }),
result: jsonValueSchema.parse(result), result,
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
relationshipCoverage: { status: "complete" }, relationshipCoverage: { status: "complete" },
+6 -8
View File
@@ -35,11 +35,9 @@ const artifactComparison = createEvidence(
left_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.left.evidence_id], left_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.left.evidence_id],
right_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.right.evidence_id], right_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.right.evidence_id],
}, },
result: jsonValueSchema.parse( result: compareArtifacts(
compareArtifacts( ARTIFACT_COMPARISON_EXAMPLE.left,
ARTIFACT_COMPARISON_EXAMPLE.left, ARTIFACT_COMPARISON_EXAMPLE.right,
ARTIFACT_COMPARISON_EXAMPLE.right,
),
), ),
confidence: "derived", confidence: "derived",
authority: "analyst-inference", authority: "analyst-inference",
@@ -157,10 +155,10 @@ it("keeps text, assembly and unavailable CFG caveats on their selected correlati
{ {
operation: source.operation, operation: source.operation,
parameters: source.parameters, parameters: source.parameters,
result: jsonValueSchema.parse({ result: {
...functionDossierSchema.parse(source.normalized_result), ...functionDossierSchema.parse(source.normalized_result),
basic_blocks: [{ start: "0x1000", end: "0x1000", successors: [] }], basic_blocks: [{ start: "0x1000", end: "0x1000", successors: [] }],
}), },
confidence: source.confidence, confidence: source.confidence,
authority: source.authority, authority: source.authority,
}, },
@@ -178,7 +176,7 @@ it("keeps text, assembly and unavailable CFG caveats on their selected correlati
left_evidence_id: left.evidence_id, left_evidence_id: left.evidence_id,
right_evidence_id: right.evidence_id, right_evidence_id: right.evidence_id,
}, },
result: jsonValueSchema.parse(computed), result: computed,
confidence: "derived", confidence: "derived",
authority: "analyst-inference", authority: "analyst-inference",
limitations: computed.limitations, limitations: computed.limitations,
+1 -2
View File
@@ -6,7 +6,6 @@ import {
parseEvidenceBundle, parseEvidenceBundle,
serializeEvidenceBundle, serializeEvidenceBundle,
} from "./evidenceBundle.js"; } from "./evidenceBundle.js";
import { jsonValueSchema } from "./jsonValue.js";
import { import {
webMcpDiscoverySchema, webMcpDiscoverySchema,
type WebMcpDiscovery, type WebMcpDiscovery,
@@ -106,7 +105,7 @@ describe("WebMCP discovery consequential annotation", () => {
predicateType: "rea.webmcp-discovery", predicateType: "rea.webmcp-discovery",
operation: "discover_webmcp_tools", operation: "discover_webmcp_tools",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(result), result,
}); });
const bundle = createEvidenceBundle([evidence]); const bundle = createEvidenceBundle([evidence]);
const reread = parseEvidenceBundle( const reread = parseEvidenceBundle(
@@ -14,7 +14,7 @@ import {
parseEvidence, parseEvidence,
type Evidence, type Evidence,
} from "../../../src/domain/evidence.js"; } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js"; import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js"; import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
@@ -75,7 +75,7 @@ const assertCoverageControls = async ({
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(result), result: result,
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -1,4 +1,4 @@
import { rm, writeFile } from "node:fs/promises"; import { writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { pathToFileURL } from "node:url"; import { pathToFileURL } from "node:url";
@@ -145,7 +145,6 @@ it("closes an Electron inspection session when cleanup is cancelled", async () =
}); });
onTestFinished(async () => { onTestFinished(async () => {
await browser.close(); await browser.close();
await rm(root, { recursive: true, force: true });
}); });
const controller = new AbortController(); const controller = new AbortController();
const pending = new CdpElectronProvider().inspectPage( const pending = new CdpElectronProvider().inspectPage(
@@ -204,7 +203,6 @@ it("classifies cancellation that arrives during final Electron detach", async ()
}); });
onTestFinished(async () => { onTestFinished(async () => {
await browser.close(); await browser.close();
await rm(root, { recursive: true, force: true });
}); });
const result = await new CdpElectronProvider().inspectPage( const result = await new CdpElectronProvider().inspectPage(
@@ -283,7 +281,6 @@ it.each(
closing.mockRestore(); closing.mockRestore();
await provider.close(); await provider.close();
await browser.close(); await browser.close();
await rm(root, { recursive: true, force: true });
}); });
const result = const result =
provider instanceof CdpBrowserProvider provider instanceof CdpBrowserProvider
@@ -1,4 +1,4 @@
import { rm, writeFile } from "node:fs/promises"; import { writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { pathToFileURL } from "node:url"; import { pathToFileURL } from "node:url";
@@ -52,7 +52,6 @@ it.each([
| undefined; | undefined;
if (closeProvider !== undefined) await closeProvider.call(provider); if (closeProvider !== undefined) await closeProvider.call(provider);
await browser.close(); await browser.close();
await rm(root, { recursive: true, force: true });
}); });
const close = vi const close = vi
@@ -36,7 +36,7 @@ const observe = async (path: string) => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventory), result: inventory,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -62,7 +62,7 @@ const inspectPages = (
{ {
operation: "inspect_artifact", operation: "inspect_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
inspection_id: `ai_${"f".repeat(64)}`, inspection_id: `ai_${"f".repeat(64)}`,
subject: { subject: {
manifest_id: inventory.manifest.manifest_id, manifest_id: inventory.manifest.manifest_id,
@@ -96,7 +96,7 @@ const inspectPages = (
}, },
evidence_links: pages.map(({ evidence_id }) => evidence_id), evidence_links: pages.map(({ evidence_id }) => evidence_id),
limitations: [], limitations: [],
}), },
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -144,7 +144,7 @@ describe("leaf artifact comparison", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(withoutCoverage), result: withoutCoverage,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -306,11 +306,11 @@ describe("artifact inventory coverage", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...graph, ...graph,
coverage: { scope: "root", status: "unknown" }, coverage: { scope: "root", status: "unknown" },
limitations: ["No extra child entries were observed."], limitations: ["No extra child entries were observed."],
}), },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -348,10 +348,10 @@ describe("artifact inventory coverage", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: fragment.parameters, parameters: fragment.parameters,
result: jsonValueSchema.parse({ result: {
...fragmentGraph, ...fragmentGraph,
coverage: { ...fragmentGraph.coverage, status: "unknown" }, coverage: { ...fragmentGraph.coverage, status: "unknown" },
}), },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -448,10 +448,10 @@ describe("artifact comparison", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...completeInventory, ...completeInventory,
nodes: [], nodes: [],
}), },
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
limitations: ["Inventory node observations are unavailable."], limitations: ["Inventory node observations are unavailable."],
@@ -496,10 +496,10 @@ describe("artifact comparison completeness", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...inventory, ...inventory,
coverage: { scope: "root", status: "complete" }, coverage: { scope: "root", status: "complete" },
}), },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -862,10 +862,10 @@ describe("artifact comparison fragment integrity", () => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...artifactInventoryResultSchema.parse(fragment.normalized_result), ...artifactInventoryResultSchema.parse(fragment.normalized_result),
coverage: { scope: "root", status: "complete" }, coverage: { scope: "root", status: "complete" },
}), },
}, },
); );
expect(() => expect(() =>
@@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
import { inventoryArtifact } from "../../fixtures/artifactInventory.js"; import { inventoryArtifact } from "../../fixtures/artifactInventory.js";
import { compareArtifacts } from "../../../src/domain/artifactComparison.js"; import { compareArtifacts } from "../../../src/domain/artifactComparison.js";
import { createEvidence } from "../../../src/domain/evidence.js"; import { createEvidence } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const observe = async ( const observe = async (
@@ -30,7 +30,7 @@ const observe = async (
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventory), result: inventory,
confidence: partial ? "derived" : "observed", confidence: partial ? "derived" : "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -10,7 +10,7 @@ import { inventoryArtifact } from "../../fixtures/artifactInventory.js";
import { compareArtifacts } from "../../../src/domain/artifactComparison.js"; import { compareArtifacts } from "../../../src/domain/artifactComparison.js";
import type { ArtifactInventoryResult } from "../../../src/domain/artifactGraph.js"; import type { ArtifactInventoryResult } from "../../../src/domain/artifactGraph.js";
import { createEvidence } from "../../../src/domain/evidence.js"; import { createEvidence } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const observeZip = async (entries: readonly (readonly [string, string])[]) => { const observeZip = async (entries: readonly (readonly [string, string])[]) => {
@@ -32,7 +32,7 @@ const observeZip = async (entries: readonly (readonly [string, string])[]) => {
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventory), result: inventory,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -1360,6 +1360,30 @@ it.each([
}, },
); );
it.each(["knip:production", "lint:typed"])(
"keeps the %s analysis command within static checks",
async (script) => {
const { directory } = await fixture();
const base = await change(
directory,
["package.json"],
JSON.stringify({ scripts: { [script]: "node scripts/old.mjs" } }),
);
const head = await change(
directory,
["package.json"],
JSON.stringify({ scripts: { [script]: "node scripts/new.mjs" } }),
);
expect(await classify(directory, base, head)).toMatchObject({
code: true,
runtime: false,
package: false,
evm: false,
browser: false,
});
},
);
it("retains a broad fallback when a provider script adds shared shell work", async () => { it("retains a broad fallback when a provider script adds shared shell work", async () => {
const { directory } = await fixture(); const { directory } = await fixture();
const before = { const before = {
@@ -14,7 +14,7 @@ import {
import { reconcileJavaScriptRuntimeEvidence } from "../../../src/application/javascript/JavaScriptRuntimeReconciliationService.js"; import { reconcileJavaScriptRuntimeEvidence } from "../../../src/application/javascript/JavaScriptRuntimeReconciliationService.js";
import { createEvidence } from "../../../src/domain/evidence.js"; import { createEvidence } from "../../../src/domain/evidence.js";
import { AnalysisInputError } from "../../../src/domain/analysisErrorCore.js"; import { AnalysisInputError } from "../../../src/domain/analysisErrorCore.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js"; import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { import {
applicationVersionComparisonResultSchema, applicationVersionComparisonResultSchema,
@@ -377,10 +377,10 @@ describe("Application graph Evidence envelopes", () => {
predicateType: source.predicate_type, predicateType: source.predicate_type,
operation: source.operation, operation: source.operation,
parameters: source.parameters, parameters: source.parameters,
result: jsonValueSchema.parse({ result: {
...result, ...result,
evidence_links: [...result.evidence_links, extraLink], evidence_links: [...result.evidence_links, extraLink],
}), },
rawResult: source.raw_result, rawResult: source.raw_result,
confidence: source.confidence, confidence: source.confidence,
authority: source.authority, authority: source.authority,
@@ -479,7 +479,7 @@ describe("JavaScript application workflow error diagnostics", () => {
predicateType: evidence.predicate_type, predicateType: evidence.predicate_type,
operation: evidence.operation, operation: evidence.operation,
parameters: evidence.parameters, parameters: evidence.parameters,
result: jsonValueSchema.parse(malformedResult), result: malformedResult,
confidence: evidence.confidence, confidence: evidence.confidence,
authority: evidence.authority, authority: evidence.authority,
limitations: evidence.limitations, limitations: evidence.limitations,
@@ -0,0 +1,270 @@
import { parse } from "@babel/parser";
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { expect } from "vitest";
import { z } from "zod";
import { writeRepositoryOxlintConfiguration } from "../../fixtures/oxlintConfiguration.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { processTest } from "../../support/process/processFixture.js";
const repository = fileURLToPath(new URL("../../../", import.meta.url));
const oxlint = join(repository, "node_modules/oxlint/bin/oxlint");
const applicationSource = "src/application/lintRuleAcceptance.ts";
const testSource = "tests/boundary/lintRuleAcceptance.test.ts";
interface AcceptanceCase {
readonly name: string;
readonly filename: string;
readonly source: string;
readonly exitCode: number;
readonly diagnostics: readonly {
readonly code?: string;
readonly severity: "error" | "warning";
readonly message?: string;
}[];
}
const cases: readonly AcceptanceCase[] = [
{
name: "enables imported Evidence JSON admission errors through the repository plugin",
filename: applicationSource,
source: `
import { createEvidence as record } from "../domain/evidence.js";
import { jsonValueSchema as json } from "../domain/jsonValue.js";
export const evidence = record(undefined, {}, { result: json.parse({ ready: true }) });
`,
exitCode: 1,
diagnostics: [
{ code: "rea(no-evidence-json-preadmission)", severity: "error" },
],
},
{
name: "recognizes canonical Evidence owners imported through file URLs",
filename: applicationSource,
source: `
import { createEvidence as record } from ${JSON.stringify(pathToFileURL(join(repository, "src/domain/evidence.js")).href)};
import { jsonValueSchema as json } from ${JSON.stringify(pathToFileURL(join(repository, "src/domain/jsonValue.js")).href)};
export const evidence = record(undefined, {}, { result: json.parse({ ready: true }) });
`,
exitCode: 1,
diagnostics: [
{ code: "rea(no-evidence-json-preadmission)", severity: "error" },
],
},
{
name: "allows semantic schema admission and stored JSON contracts",
filename: applicationSource,
source: `
import { z } from "zod";
import { createEvidence } from "../domain/evidence.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
const semanticSchema = z.object({ ready: z.boolean() });
export const semantic = createEvidence(undefined, {}, { result: semanticSchema.parse({ ready: true }) });
const result = jsonValueSchema.parse({ ready: true });
export const stored = createEvidence(undefined, {}, { result });
`,
exitCode: 0,
diagnostics: [],
},
{
name: "enables positional catalog type errors through the repository plugin",
filename: applicationSource,
source: `
import { GPU_TOOL_CONTRACTS as contracts } from "../contracts/gpu/gpuToolContracts.js";
export type Selected = (typeof contracts)[0];
`,
exitCode: 1,
diagnostics: [
{ code: "rea(no-positional-tool-contract)", severity: "error" },
],
},
{
name: "enables duplicate fixture root teardown errors through the repository plugin",
filename: testSource,
source: `
import { afterEach } from "vitest";
import { rm } from "node:fs/promises";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
const root = await createTestTempDirectory("rea-lint-rule-");
afterEach(() => rm(root, { recursive: true, force: true }));
`,
exitCode: 1,
diagnostics: [{ code: "rea(no-fixture-root-cleanup)", severity: "error" }],
},
{
name: "allows explicit root deletion outside fixture teardown",
filename: testSource,
source: `
import { rm } from "node:fs/promises";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
const root = await createTestTempDirectory("rea-lint-rule-");
await rm(root, { recursive: true, force: true });
`,
exitCode: 0,
diagnostics: [],
},
{
name: "rejects an unreturned promise callback containing assertions",
filename: testSource,
source: `
import { expect, it } from "vitest";
it("unjoined assertion", () => {
Promise.resolve("ready").then(value => { expect(value).toBe("ready"); });
});
`,
exitCode: 1,
diagnostics: [
{ code: "vitest(valid-expect-in-promise)", severity: "error" },
],
},
{
name: "rejects focused tests through the repository Vitest configuration",
filename: testSource,
source: `
import { expect, it } from "vitest";
it.only("focused assertion", () => { expect("ready").toBe("ready"); });
`,
exitCode: 1,
diagnostics: [{ code: "vitest(no-focused-tests)", severity: "error" }],
},
{
name: "keeps native stored assertion limitations at warning severity",
filename: testSource,
source: `
import { expect, it } from "vitest";
it("joins a stored assertion", async () => {
const assertion = expect(Promise.reject(new Error("controlled"))).rejects.toThrow("controlled");
await assertion;
});
`,
exitCode: 0,
diagnostics: [{ code: "vitest(valid-expect)", severity: "warning" }],
},
{
name: "allows an assertion diagnostic message",
filename: testSource,
source: `
import { expect, it } from "vitest";
it("keeps diagnostic context", () => {
expect("ready", "controlled diagnostic context").toBe("ready");
});
`,
exitCode: 0,
diagnostics: [],
},
{
name: "rejects unused suppression directives through the repository configuration",
filename: testSource,
source: `
// oxlint-disable-next-line no-var
export const ready = true;
`,
exitCode: 1,
diagnostics: [
{
severity: "error",
message: "Unused oxlint-disable directive (no problems were reported).",
},
],
},
];
const reportSchema = z.object({
diagnostics: z.array(
z.object({
code: z.string().optional(),
severity: z.enum(["error", "warning"]),
message: z.string(),
}),
),
});
const relocateImports = (scenario: AcceptanceCase, path: string): string => {
const source = scenario.source;
const parsed = parse(source, {
sourceType: "module",
plugins: ["typescript"],
});
const replacements = [];
for (const statement of parsed.program.body) {
if (
statement.type !== "ImportDeclaration" ||
!statement.source.value.startsWith(".")
)
continue;
const target = resolve(
repository,
dirname(scenario.filename),
statement.source.value,
);
const relativePath = relative(dirname(path), target);
const specifier = isAbsolute(relativePath)
? pathToFileURL(target).href
: relativePath.startsWith(".")
? relativePath.split(sep).join("/")
: `./${relativePath.split(sep).join("/")}`;
const { start, end } = statement.source;
if (
start === null ||
start === undefined ||
end === null ||
end === undefined
)
throw new Error("Authored lint fixture import has no source range");
replacements.push({
start,
end,
specifier,
});
}
let relocated = source;
for (const replacement of replacements.reverse())
relocated =
relocated.slice(0, replacement.start) +
JSON.stringify(replacement.specifier) +
relocated.slice(replacement.end);
return relocated;
};
for (const scenario of cases) {
processTest(scenario.name, async ({ processes }) => {
const project = await createTestTempDirectory("rea-lint-configuration-");
const configuration = await writeRepositoryOxlintConfiguration(project);
const path = join(project, scenario.filename);
await mkdir(dirname(path), { recursive: true });
await writeFile(path, relocateImports(scenario, path));
const result = await processes.run(
process.execPath,
[
oxlint,
"--config",
configuration,
"--threads",
"1",
"--format",
"json",
scenario.filename,
],
{
cwd: project,
timeoutMs: 15_000,
},
);
expect(result.signal, result.stderr).toBeNull();
expect(result.exitCode, result.stderr || result.stdout).toBe(
scenario.exitCode,
);
const { diagnostics } = reportSchema.parse(JSON.parse(result.stdout));
expect(
diagnostics.map(({ code, severity }) => ({ code, severity })),
).toEqual(
scenario.diagnostics.map(({ code, severity }) => ({ code, severity })),
);
for (const [index, expected] of scenario.diagnostics.entries()) {
if (expected.message !== undefined)
expect(diagnostics[index]?.message).toBe(expected.message);
}
});
}
@@ -0,0 +1,117 @@
import { spawnSync } from "node:child_process";
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { expect, it } from "vitest";
import { parseProductionReachabilityReport } from "../../../scripts/ci/production-reachability-report.mjs";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const script = fileURLToPath(
new URL(
"../../../scripts/ci/production-reachability-report.mjs",
import.meta.url,
),
);
const configuration = new URL("../../../knip.production.json", import.meta.url);
const reportPath = (root: string) =>
join(root, ".cache/static-checks/production-reachability.json");
const invoke = (root: string) =>
spawnSync(process.execPath, [script], {
cwd: root,
encoding: "utf8",
timeout: 15_000,
});
it("reports test-only candidates without losing dynamic, worker or published roots", async () => {
const root = await createTestTempDirectory("rea-production-reachability-");
const files = {
"package.json": JSON.stringify({
name: "reachability-fixture",
type: "module",
bin: { fixture: "scripts/rea.mjs" },
exports: { "./published": "./src/published.ts" },
}),
"src/main.ts":
'import { live } from "./facts.js"; console.log(live); void import("./lazy.js");',
"src/facts.ts": "export const live = 1; export const testOnly = 2;",
"src/lazy.ts": "console.log('dynamic production module');",
"src/testOnly.ts": "export const retired = 3;",
"src/published.ts": "export const publicContract = 4;",
"src/composition/javascriptAnalysisWorkerProcess.ts":
'import { workerFact } from "../workerFacts.js"; console.log(workerFact);',
"src/workerFacts.ts": "export const workerFact = 5;",
"scripts/rea.mjs": 'import "../src/main.js";',
"tests/facts.test.ts":
'import { testOnly } from "../src/facts.js"; import { retired } from "../src/testOnly.js"; console.log(testOnly, retired);',
};
for (const [path, content] of Object.entries(files)) {
const destination = join(root, path);
await mkdir(dirname(destination), { recursive: true });
await writeFile(destination, content);
}
await writeFile(
join(root, "knip.production.json"),
await readFile(configuration, "utf8"),
);
const result = invoke(root);
expect(result.error).toBeUndefined();
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain("advisory candidates");
const { issues } = parseProductionReachabilityReport(
await readFile(reportPath(root), "utf8"),
);
expect(issues).toEqual(
expect.arrayContaining([
expect.objectContaining({
file: "src/facts.ts",
exports: expect.arrayContaining([
expect.objectContaining({ name: "testOnly" }),
]),
}),
expect.objectContaining({ file: "src/testOnly.ts" }),
]),
);
const reached = [
"src/main.ts",
"src/lazy.ts",
"src/published.ts",
"src/workerFacts.ts",
"src/composition/javascriptAnalysisWorkerProcess.ts",
"tests/facts.test.ts",
];
expect(issues.filter(({ file }) => reached.includes(file))).toEqual([]);
expect(
issues.flatMap(({ exports: findings = [] }) =>
findings.map(({ name }) => name),
),
).not.toContain("live");
});
it("fails on Knip configuration errors and removes a stale candidate report", async () => {
const root = await createTestTempDirectory(
"rea-production-reachability-error-",
);
await writeFile(join(root, "package.json"), '{"name":"broken-config"}');
await writeFile(join(root, "knip.production.json"), "{");
await mkdir(dirname(reportPath(root)), { recursive: true });
await writeFile(reportPath(root), '{"issues":[]}');
const result = invoke(root);
expect(result.error).toBeUndefined();
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("Production reachability analysis failed");
await expect(access(reportPath(root))).rejects.toMatchObject({
code: "ENOENT",
});
});
it.each([
"not JSON",
'{"issues":[{"file":"src/facts.ts","exports":[{"name":42}]}]}',
'{"issues":[{"file":"src/facts.ts","unrecognized":[]}]}',
])("rejects malformed checker output: %s", (text) => {
expect(() => parseProductionReachabilityReport(text)).toThrow();
});
@@ -1,10 +1,11 @@
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises"; import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join, sep } from "node:path"; import { dirname, join, sep } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { expect, it } from "vitest"; import { expect, it } from "vitest";
import { z } from "zod"; import { z } from "zod";
import { writeRepositoryOxlintConfiguration } from "../../fixtures/oxlintConfiguration.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const script = fileURLToPath( const script = fileURLToPath(
@@ -149,11 +150,11 @@ it("keeps lint errors failing and visible alongside the retained warnings", asyn
it("excludes translation staging while retaining authored website errors", async () => { it("excludes translation staging while retaining authored website errors", async () => {
const root = await fixture(); const root = await fixture();
for (const name of [".gitignore", ".oxlintrc.json"]) await writeFile(
await writeFile(join(root, name), await readFile(join(repository, name))); join(root, ".gitignore"),
await cp(join(repository, "scripts/lint"), join(root, "scripts/lint"), { await readFile(join(repository, ".gitignore")),
recursive: true, );
}); await writeRepositoryOxlintConfiguration(root);
const authored = "website/public/assets/authored.js"; const authored = "website/public/assets/authored.js";
const staged = const staged =
"website/.i18n-regression/site/examples/notes-electron/main.js"; "website/.i18n-regression/site/examples/notes-electron/main.js";
@@ -166,7 +167,7 @@ it("excludes translation staging while retaining authored website errors", async
); );
} }
const result = invoke(root, "lint", "website"); const result = invoke(root, "lint");
expect(result.error).toBeUndefined(); expect(result.error).toBeUndefined();
expect(result.status).toBe(1); expect(result.status).toBe(1);
expect(result.stdout).toContain("1 errors, 0 warnings"); expect(result.stdout).toContain("1 errors, 0 warnings");
@@ -0,0 +1,192 @@
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { dirname, isAbsolute, join, relative, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { expect } from "vitest";
import { z } from "zod";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { writeRepositoryOxlintConfiguration } from "../../fixtures/oxlintConfiguration.js";
import { processTest } from "../../support/process/processFixture.js";
const dependencies = fileURLToPath(
new URL("../../../node_modules", import.meta.url),
);
const reporter = fileURLToPath(
new URL("../../../scripts/ci/static-check-report.mjs", import.meta.url),
);
const sources = {
"src/unjoined.test.ts": `
import { expect, it } from "vitest";
const cliTest = it.extend<{ label: string }>({ label: "owned" });
it("forgets the assertion promise", () => {
expect(Promise.resolve("ready")).resolves.toBe("ready");
});
cliTest("forgets the assertion in an extended callback", ({ label }) => {
expect(Promise.resolve(label)).resolves.toBe("owned");
});
`,
"src/joined.test.ts": `
import { expect, it } from "vitest";
const cliTest = it.extend<{ label: string }>({ label: "owned" });
it("joins a stored assertion promise", async () => {
const assertion = expect(Promise.resolve("ready")).resolves.toBe("ready");
await assertion;
});
cliTest("joins a stored assertion in an extended callback", async ({ label }) => {
const assertion = expect(Promise.resolve(label)).resolves.toBe("owned");
await assertion;
});
export async function finishBeforeCleanup(): Promise<string> {
try {
return await Promise.resolve("ready");
} finally {
await Promise.resolve();
}
}
`,
"src/misused.ts": `
export function promiseCondition(): string {
if (Promise.resolve(false)) return "ready";
return "pending";
}
`,
"src/nonexhaustive.ts": `
type Outcome = { kind: "open" } | { kind: "closed" };
export function describeOutcome(outcome: Outcome): string {
switch (outcome.kind) {
case "open": return "open";
default: return "unknown";
}
}
`,
"src/unawaited-finally.ts": `
export async function cleanupBeforeCompletion(): Promise<string> {
try {
return Promise.resolve("ready");
} finally {
await Promise.resolve();
}
}
`,
"src/test-misused.test.ts": `
import { it } from "vitest";
it("keeps test-only promise conditions advisory", () => {
if (Promise.resolve(false)) throw new Error("test fixture");
});
`,
"tests/unjoined.test.ts": `
import { expect, it } from "vitest";
const cliTest = it.extend<{ label: string }>({ label: "owned" });
it("warns about an unjoined boundary-test assertion", () => {
expect(Promise.resolve("ready")).resolves.toBe("ready");
});
cliTest("warns in an extended boundary-test callback", ({ label }) => {
expect(Promise.resolve(label)).resolves.toBe("owned");
});
`,
};
processTest(
"runs typed promise and exhaustiveness rules on an isolated real TypeScript project",
async ({ processes }) => {
const root = await createTestTempDirectory("rea-typed-lint-");
await symlink(
dependencies,
join(root, "node_modules"),
process.platform === "win32" ? "junction" : "dir",
);
await Promise.all(
Object.keys(sources).map((name) =>
mkdir(dirname(join(root, name)), { recursive: true }),
),
);
await writeRepositoryOxlintConfiguration(root);
await Promise.all([
writeFile(join(root, "package.json"), '{"type":"module"}\n'),
writeFile(
join(root, "tsconfig.json"),
JSON.stringify({
compilerOptions: {
strict: true,
noEmit: true,
skipLibCheck: true,
module: "NodeNext",
moduleResolution: "NodeNext",
target: "ES2023",
types: ["node"],
},
files: Object.keys(sources),
}),
),
...Object.entries(sources).map(([name, source]) =>
writeFile(join(root, name), source),
),
]);
const result = await processes.run(
process.execPath,
[reporter, "typed-lint", ...Object.keys(sources)],
{
cwd: root,
timeoutMs: 20_000,
env: { ...process.env, GOMAXPROCS: "2", GOMEMLIMIT: "1GiB" },
},
);
expect(result.signal, result.stderr).toBeNull();
expect(result.exitCode, result.stderr || result.stdout).toBe(1);
const { diagnostics } = z
.object({
diagnostics: z.array(
z.object({
code: z.string(),
severity: z.enum(["error", "warning"]),
filename: z.string(),
}),
),
})
.parse(
JSON.parse(
await readFile(
join(root, ".cache/static-checks/typed-oxlint.json"),
"utf8",
),
),
);
expect(
diagnostics
.filter(({ code }) =>
[
"typescript(no-floating-promises)",
"typescript(no-misused-promises)",
"typescript(return-await)",
"typescript(switch-exhaustiveness-check)",
].includes(code),
)
.map(
({ code, filename, severity }) =>
`${(isAbsolute(filename) ? relative(root, filename) : filename).split(sep).join("/")}: ${severity}: ${code}`,
)
.sort(),
).toEqual([
"src/misused.ts: error: typescript(no-misused-promises)",
"src/nonexhaustive.ts: warning: typescript(switch-exhaustiveness-check)",
"src/test-misused.test.ts: warning: typescript(no-misused-promises)",
"src/unawaited-finally.ts: warning: typescript(return-await)",
"src/unjoined.test.ts: error: typescript(no-floating-promises)",
"src/unjoined.test.ts: error: typescript(no-floating-promises)",
"tests/unjoined.test.ts: warning: typescript(no-floating-promises)",
"tests/unjoined.test.ts: warning: typescript(no-floating-promises)",
]);
expect(
diagnostics.filter(({ severity }) => severity === "error"),
).toHaveLength(3);
},
);
@@ -1,4 +1,4 @@
import { mkdir, rm, writeFile } from "node:fs/promises"; import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { expect, it, onTestFinished, vi } from "vitest"; import { expect, it, onTestFinished, vi } from "vitest";
@@ -43,12 +43,11 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("retains cancelled source read ownership until service close retries it", async () => { it("retains cancelled source read ownership until service close retries it", async () => {
const root = await createTestTempDirectory("rea-web-artifact-read-owner-"); const root = await createTestTempDirectory("rea-web-artifact-read-owner-");
onTestFinished(async () => { onTestFinished(() => {
injected.sourcePath = undefined; injected.sourcePath = undefined;
injected.abortController = undefined; injected.abortController = undefined;
injected.failClose = false; injected.failClose = false;
injected.closeAttempts = 0; injected.closeAttempts = 0;
await rm(root, { recursive: true, force: true });
}); });
const source = 'import "./dep.js";'; const source = 'import "./dep.js";';
const fixture = webModuleArtifactsFixture(source); const fixture = webModuleArtifactsFixture(source);
@@ -20,7 +20,7 @@ import {
} from "../../../src/domain/evidence.js"; } from "../../../src/domain/evidence.js";
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js"; import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { import {
analysisViewResultSchema, analysisViewResultSchema,
type AnalysisViewResult, type AnalysisViewResult,
@@ -446,10 +446,10 @@ it.each(["full", "compact"] as const)(
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...graph, ...graph,
occurrences: graph.occurrences.slice(0, 1), occurrences: graph.occurrences.slice(0, 1),
}), },
}, },
); );
const partialReply = await client.callTool({ const partialReply = await client.callTool({
+4 -4
View File
@@ -12,7 +12,7 @@ import {
} from "../../../src/domain/evidence.js"; } from "../../../src/domain/evidence.js";
import { EvidenceIntegrityError } from "../../../src/domain/evidenceErrors.js"; import { EvidenceIntegrityError } from "../../../src/domain/evidenceErrors.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js"; import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
import { import {
artifactEdgeId, artifactEdgeId,
@@ -468,7 +468,7 @@ it("records an explicitly continued mismatch, preserves verified siblings, and n
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventoryWithoutCoverage), result: inventoryWithoutCoverage,
}, },
); );
const retainedComparison = await client.callTool({ const retainedComparison = await client.callTool({
@@ -913,7 +913,7 @@ const withForeignMetadataAndNestedZipMember = (
parameters: evidence.parameters, parameters: evidence.parameters,
confidence: evidence.confidence, confidence: evidence.confidence,
authority: evidence.authority, authority: evidence.authority,
result: jsonValueSchema.parse({ result: {
...inventory, ...inventory,
occurrences, occurrences,
edges, edges,
@@ -924,7 +924,7 @@ const withForeignMetadataAndNestedZipMember = (
graph_sha256: graphSha256, graph_sha256: graphSha256,
manifest_id: manifestId, manifest_id: manifestId,
}, },
}), },
}, },
); );
}; };
@@ -12,7 +12,7 @@ import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js"; import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
import { createEvidence } from "../../../src/domain/evidence.js"; import { createEvidence } from "../../../src/domain/evidence.js";
import { ANALYSIS_SNAPSHOT_TARGET } from "../../../src/domain/analysisSnapshot.fixture.js"; import { ANALYSIS_SNAPSHOT_TARGET } from "../../../src/domain/analysisSnapshot.fixture.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js"; import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js";
import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js"; import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js";
import { createServer } from "../../../src/server/createServer.js"; import { createServer } from "../../../src/server/createServer.js";
@@ -195,7 +195,7 @@ const createBundleSourceSession = () => {
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE), result: EMPTY_PROCESS_CAPTURE_EXAMPLE,
confidence: "observed", confidence: "observed",
authority: "controlled-replay", authority: "controlled-replay",
}), }),
@@ -14,7 +14,7 @@ import {
type FunctionComparisonResult, type FunctionComparisonResult,
} from "../../../src/domain/functionComparisonSchemas.js"; } from "../../../src/domain/functionComparisonSchemas.js";
import { summarize } from "../../../src/domain/functionComparisonResults.js"; import { summarize } from "../../../src/domain/functionComparisonResults.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js"; import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
@@ -165,7 +165,7 @@ it.each([
predicateType: base.predicate_type, predicateType: base.predicate_type,
operation: base.operation, operation: base.operation,
parameters: base.parameters, parameters: base.parameters,
result: jsonValueSchema.parse(scenario.result(result)), result: scenario.result(result),
confidence: base.confidence, confidence: base.confidence,
authority: base.authority, authority: base.authority,
limitations: base.limitations, limitations: base.limitations,
@@ -9,7 +9,6 @@ import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarnes
import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js"; import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
import { compareFunctions } from "../../../src/domain/functionComparison.js"; import { compareFunctions } from "../../../src/domain/functionComparison.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
@@ -74,7 +73,7 @@ it.each([
{ {
operation: "analyze_function", operation: "analyze_function",
parameters: { procedure: entry }, parameters: { procedure: entry },
result: jsonValueSchema.parse(value), result: value,
confidence: "derived", confidence: "derived",
relationshipCoverage: { status: "complete" }, relationshipCoverage: { status: "complete" },
}, },
@@ -532,14 +531,14 @@ it.each([
}; };
const consistent = createEvidence(undefined, provider, { const consistent = createEvidence(undefined, provider, {
...observation, ...observation,
result: jsonValueSchema.parse(result), result: result,
}); });
const inconsistent = const inconsistent =
status === "unchanged" status === "unchanged"
? null ? null
: createEvidence(undefined, provider, { : createEvidence(undefined, provider, {
...observation, ...observation,
result: jsonValueSchema.parse({ ...result, status: "unchanged" }), result: { ...result, status: "unchanged" },
}); });
const directory = await createTestTempDirectory("rea-function-status-"); const directory = await createTestTempDirectory("rea-function-status-");
const path = join(directory, "bundle.json"); const path = join(directory, "bundle.json");
@@ -11,7 +11,7 @@ import {
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { functionComparisonResultSchema } from "../../../src/domain/functionComparisonSchemas.js"; import { functionComparisonResultSchema } from "../../../src/domain/functionComparisonSchemas.js";
import { functionDossierSchema } from "../../../src/domain/hopperValues.js"; import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { reconstructionVerificationResultSchema } from "../../../src/domain/reconstructionVerificationSchemas.js"; import { reconstructionVerificationResultSchema } from "../../../src/domain/reconstructionVerificationSchemas.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js"; import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js";
@@ -147,7 +147,7 @@ const evidence = (
? {} ? {}
: { analysisProfile: options.profile ?? profile }), : { analysisProfile: options.profile ?? profile }),
parameters: { ...example.parameters, observation: id }, parameters: { ...example.parameters, observation: id },
result: jsonValueSchema.parse({ result: {
...dossier, ...dossier,
procedure: { procedure: {
...dossier.procedure, ...dossier.procedure,
@@ -162,7 +162,7 @@ const evidence = (
callers: [...(options.callers ?? [])], callers: [...(options.callers ?? [])],
incoming_references: [...(options.incoming ?? [])], incoming_references: [...(options.incoming ?? [])],
outgoing_references: [...outgoing], outgoing_references: [...outgoing],
}), },
confidence: "derived", confidence: "derived",
authority: "shipped-artifact", authority: "shipped-artifact",
relationshipCoverage: { status: "complete" }, relationshipCoverage: { status: "complete" },
@@ -8,7 +8,7 @@ import {
functionComparisonResultSchema, functionComparisonResultSchema,
type FunctionComparisonResult as Comparison, type FunctionComparisonResult as Comparison,
} from "../../../src/domain/functionComparisonSchemas.js"; } from "../../../src/domain/functionComparisonSchemas.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createServer } from "../../../src/server/createServer.js"; import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js";
@@ -30,7 +30,7 @@ const evidence = (
{ {
operation: "analyze_function", operation: "analyze_function",
parameters: { ...template.parameters, observation: id }, parameters: { ...template.parameters, observation: id },
result: jsonValueSchema.parse({ result: {
...base, ...base,
referenced_strings: [...strings], referenced_strings: [...strings],
swift_string_literals: swift_string_literals:
@@ -47,7 +47,7 @@ const evidence = (
}, },
], ],
swift_string_literal_omissions: [], swift_string_literal_omissions: [],
}), },
confidence: template.confidence, confidence: template.confidence,
authority: template.authority, authority: template.authority,
relationshipCoverage: { status: "complete" }, relationshipCoverage: { status: "complete" },
@@ -1,4 +1,4 @@
import { parseEvidence } from "../../../src/domain/evidence.js"; import { parseEvidence, createEvidence } from "../../../src/domain/evidence.js";
import { ok as resultOk } from "../../../src/domain/result.js"; import { ok as resultOk } from "../../../src/domain/result.js";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { expect, it } from "vitest"; import { expect, it } from "vitest";
@@ -14,18 +14,14 @@ import {
} from "../../../src/application/AnalysisProvider.js"; } from "../../../src/application/AnalysisProvider.js";
import type { BinarySession } from "../../../src/application/binary/BinarySession.js"; import type { BinarySession } from "../../../src/application/binary/BinarySession.js";
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
import { INVESTIGATION_EXAMPLES } from "../../../src/contracts/investigationExamples.js";
import { import {
INVESTIGATION_EXAMPLES,
FUNCTION_COMPARISON_EVIDENCE, FUNCTION_COMPARISON_EVIDENCE,
PROCESS_CAPTURE_RECONSTRUCTION, PROCESS_CAPTURE_RECONSTRUCTION,
PROCESS_CAPTURE_REFERENCE, PROCESS_CAPTURE_REFERENCE,
PROCESS_COMPARISON_EVIDENCE, PROCESS_COMPARISON_EVIDENCE,
} from "../../../src/contracts/investigationExamples.js"; } from "../../../src/contracts/investigationExamples.js";
import { createEvidence } from "../../../src/domain/evidence.js"; import { jsonObjectSchema } from "../../../src/domain/jsonValue.js";
import {
jsonObjectSchema,
jsonValueSchema,
} from "../../../src/domain/jsonValue.js";
import { createServer } from "../../../src/server/createServer.js"; import { createServer } from "../../../src/server/createServer.js";
import { observed } from "../../fixtures/analysisExecution.js"; import { observed } from "../../fixtures/analysisExecution.js";
import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js"; import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js";
@@ -217,7 +213,7 @@ const dossierWithCallees = (addresses: readonly string[]) => {
predicateType: base.predicate_type, predicateType: base.predicate_type,
operation: base.operation, operation: base.operation,
parameters: base.parameters, parameters: base.parameters,
result: jsonValueSchema.parse({ result: {
...jsonObjectSchema.parse(base.normalized_result), ...jsonObjectSchema.parse(base.normalized_result),
callees: addresses.map((address) => ({ callees: addresses.map((address) => ({
address, address,
@@ -228,7 +224,7 @@ const dossierWithCallees = (addresses: readonly string[]) => {
reason: "The fixture has no callee body-extent observation.", reason: "The fixture has no callee body-extent observation.",
}, },
})), })),
}), },
rawResult: base.raw_result, rawResult: base.raw_result,
confidence: base.confidence, confidence: base.confidence,
authority: base.authority, authority: base.authority,
@@ -5,7 +5,7 @@ import {
MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE, MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE,
} from "../../../src/contracts/managed/managedWorkflowExamples.js"; } from "../../../src/contracts/managed/managedWorkflowExamples.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { import {
managedMemberInspectionSchema, managedMemberInspectionSchema,
managedNativeBoundaryInspectionSchema, managedNativeBoundaryInspectionSchema,
@@ -34,7 +34,7 @@ const emptyExportEvidence = () => {
{ {
operation: "inspect_macho", operation: "inspect_macho",
parameters: original.parameters, parameters: original.parameters,
result: jsonValueSchema.parse({ result: {
...result, ...result,
exports: { exports: {
items: [], items: [],
@@ -42,7 +42,7 @@ const emptyExportEvidence = () => {
exhaustive: true, exhaustive: true,
limitations: [], limitations: [],
}, },
}), },
rawResult: null, rawResult: null,
limitations: [], limitations: [],
}, },
@@ -63,7 +63,7 @@ const secondDeclaration = () => {
return createEvidence(undefined, original.provider, { return createEvidence(undefined, original.provider, {
operation: "inspect_managed_native_boundaries", operation: "inspect_managed_native_boundaries",
parameters: original.parameters, parameters: original.parameters,
result: jsonValueSchema.parse({ result: {
...result, ...result,
pinvoke_imports: [ pinvoke_imports: [
{ {
@@ -74,7 +74,7 @@ const secondDeclaration = () => {
import_name: "open_native_again", import_name: "open_native_again",
}, },
], ],
}), },
rawResult: null, rawResult: null,
limitations: original.limitations, limitations: original.limitations,
}); });
@@ -128,7 +128,7 @@ const memberEvidence = (
{ {
operation: "inspect_managed_members", operation: "inspect_managed_members",
parameters: original.parameters, parameters: original.parameters,
result: jsonValueSchema.parse(normalized), result: normalized,
rawResult: null, rawResult: null,
limitations: normalized.limitations, limitations: normalized.limitations,
}, },
@@ -27,7 +27,7 @@ import {
parseEvidence, parseEvidence,
type EvidenceProvider, type EvidenceProvider,
} from "../../../src/domain/evidence.js"; } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { digestProcessCommitment } from "../../../src/domain/process/processScenario.js"; import { digestProcessCommitment } from "../../../src/domain/process/processScenario.js";
import { createServer } from "../../../src/server/createServer.js"; import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js";
@@ -45,14 +45,14 @@ const captureEvidence = (
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: { side }, parameters: { side },
result: jsonValueSchema.parse({ result: {
...observation, ...observation,
manifest: { manifest: {
...observation.manifest, ...observation.manifest,
comparison_contract: comparisonContract, comparison_contract: comparisonContract,
comparison_contract_sha256: digestProcessCommitment(comparisonContract), comparison_contract_sha256: digestProcessCommitment(comparisonContract),
}, },
}), },
confidence: "observed", confidence: "observed",
authority: "controlled-replay", authority: "controlled-replay",
}); });
@@ -523,7 +523,7 @@ it("names the side and constraint when Evidence is not a usable capture", async
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(result), result: result,
}); });
expect(parseEvidence(invalidResult)).toEqual(invalidResult); expect(parseEvidence(invalidResult)).toEqual(invalidResult);
const malformed = await client.callTool({ const malformed = await client.callTool({
@@ -565,7 +565,7 @@ it("keeps structural capture issues alongside semantic ones through MCP and the
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...rest, ...rest,
manifest, manifest,
exit: { exit: {
@@ -577,7 +577,7 @@ it("keeps structural capture issues alongside semantic ones through MCP and the
], ],
}, },
}, },
}), },
}); });
const described = const described =
"(at manifest.completed_at: Invalid input: expected string, received undefined (1 more issues))"; "(at manifest.completed_at: Invalid input: expected string, received undefined (1 more issues))";
@@ -5,7 +5,7 @@ import { expect, it, onTestFinished } from "vitest";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js"; import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js"; import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js";
import { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.js"; import { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.js";
import { compareProcessCaptures } from "../../../src/domain/process/processComparison.js"; import { compareProcessCaptures } from "../../../src/domain/process/processComparison.js";
@@ -48,11 +48,11 @@ it("rejects an inconsistent imported process comparison while retaining its Evid
left_evidence_id: left.evidence_id, left_evidence_id: left.evidence_id,
right_evidence_id: right.evidence_id, right_evidence_id: right.evidence_id,
}, },
result: jsonValueSchema.parse({ result: {
...nativeResult, ...nativeResult,
[dimension]: "contradiction", [dimension]: "contradiction",
status, status,
}), },
confidence: "derived", confidence: "derived",
authority: "analyst-inference", authority: "analyst-inference",
evidenceLinks: [left.evidence_id, right.evidence_id], evidenceLinks: [left.evidence_id, right.evidence_id],
@@ -11,7 +11,7 @@ import { compareProcessEvidenceFiles } from "../../../src/application/process/Pr
import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js"; import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js";
import { processTraceCaptureFixture } from "../../../src/domain/process/processCapture.fixture.js"; import { processTraceCaptureFixture } from "../../../src/domain/process/processCapture.fixture.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createServer } from "../../../src/server/createServer.js"; import { createServer } from "../../../src/server/createServer.js";
import { observed as ok } from "../../fixtures/analysisExecution.js"; import { observed as ok } from "../../fixtures/analysisExecution.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
@@ -25,7 +25,7 @@ const captureEvidence = (side: "left" | "right") =>
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: { side }, parameters: { side },
result: jsonValueSchema.parse(capture), result: capture,
confidence: "observed", confidence: "observed",
authority: "controlled-replay", authority: "controlled-replay",
locations: [{ kind: "artifact-path", path: `/fixture/${side}` }], locations: [{ kind: "artifact-path", path: `/fixture/${side}` }],
+3 -3
View File
@@ -184,7 +184,7 @@ describe("guided prompt completion from investigation records", () => {
const inspectionEvidence = createEvidence(undefined, fixtureProvider, { const inspectionEvidence = createEvidence(undefined, fixtureProvider, {
operation: "inspect_artifact", operation: "inspect_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventoryInspection), result: inventoryInspection,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
evidenceLinks: inventoryInspection.evidence_links, evidenceLinks: inventoryInspection.evidence_links,
@@ -426,7 +426,7 @@ const createPagedInspectionEvidence = async (
{ {
operation: "inventory_artifact", operation: "inventory_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(inventoryResult), result: inventoryResult,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -530,7 +530,7 @@ const createPagedInspectionEvidence = async (
{ {
operation: "inspect_artifact", operation: "inspect_artifact",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(result), result: result,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
evidenceLinks: result.evidence_links, evidenceLinks: result.evidence_links,
+2 -2
View File
@@ -18,7 +18,7 @@ import { createServer } from "../../../src/server/createServer.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.js"; import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js"; import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js"; import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js";
import { processCaptureSchema } from "../../../src/domain/process/processCapture.js"; import { processCaptureSchema } from "../../../src/domain/process/processCapture.js";
@@ -153,7 +153,7 @@ it("records capture disagreement as a contradicted unknown", async () => {
predicateType: "rea.process-capture", predicateType: "rea.process-capture",
operation: "capture_process_scenario", operation: "capture_process_scenario",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(capture), result: capture,
confidence: "observed", confidence: "observed",
authority: "controlled-replay", authority: "controlled-replay",
}); });
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { functionDossierSchema } from "../../../src/domain/hopperValues.js"; import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js"; import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
@@ -44,7 +44,7 @@ const pathSource = () => {
...template.parameters, ...template.parameters,
observation: "workflow-unknown-path", observation: "workflow-unknown-path",
}, },
result: jsonValueSchema.parse({ result: {
...dossier, ...dossier,
callees: [ callees: [
{ {
@@ -54,7 +54,7 @@ const pathSource = () => {
body: { available: false, reason: "No body was observed." }, body: { available: false, reason: "No body was observed." },
}, },
], ],
}), },
confidence: template.confidence, confidence: template.confidence,
authority: template.authority, authority: template.authority,
relationshipCoverage: { status: "complete" }, relationshipCoverage: { status: "complete" },
@@ -76,10 +76,8 @@ const incompleteRight = (suffix: string) => {
{ {
operation: "analyze_function", operation: "analyze_function",
parameters: { ...template.parameters, observation: suffix }, parameters: { ...template.parameters, observation: suffix },
result: jsonValueSchema.parse( result: functionDossierSchema.parse(
functionDossierSchema.parse( FUNCTION_COMPARISON_EXAMPLE.left.normalized_result,
FUNCTION_COMPARISON_EXAMPLE.left.normalized_result,
),
), ),
confidence: template.confidence, confidence: template.confidence,
authority: template.authority, authority: template.authority,
@@ -12,15 +12,17 @@ import {
compareManagedMemberPaths, compareManagedMemberPaths,
} from "../../../../src/application/managed/ManagedMemberComparisonService.js"; } from "../../../../src/application/managed/ManagedMemberComparisonService.js";
import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js"; import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js";
import { managedMemberComparisonResultSchema } from "../../../../src/domain/managed/managedMemberComparison.js"; import {
managedMemberComparisonResultSchema,
compareManagedMembersInputSchema,
} from "../../../../src/domain/managed/managedMemberComparison.js";
import { readManagedMemberEvidence } from "../../../../src/domain/managed/managedMemberComparisonMatch.js"; import { readManagedMemberEvidence } from "../../../../src/domain/managed/managedMemberComparisonMatch.js";
import { createEvidence } from "../../../../src/domain/evidence.js"; import { createEvidence } from "../../../../src/domain/evidence.js";
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js"; import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
import { jsonValueSchema } from "../../../../src/domain/jsonValue.js";
import { compareManagedMembersInputSchema } from "../../../../src/domain/managed/managedMemberComparison.js";
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js"; import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
import { managedPeFixtureTarget } from "../../../../src/dotnet/ManagedPe.fixture.js";
import { import {
managedPeFixtureTarget,
buildManagedNestedTypeFixture, buildManagedNestedTypeFixture,
buildManagedPeFixture, buildManagedPeFixture,
} from "../../../../src/dotnet/ManagedPe.fixture.js"; } from "../../../../src/dotnet/ManagedPe.fixture.js";
@@ -139,7 +141,7 @@ const compare = (
{ {
operation: "inspect_managed_members", operation: "inspect_managed_members",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(change?.(inspection) ?? inspection), result: change?.(inspection) ?? inspection,
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -475,14 +477,14 @@ describe("managed member comparison subject identity", () => {
{ {
operation: "inspect_managed_members", operation: "inspect_managed_members",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...inspection, ...inspection,
artifact: { ...inspection.artifact, sha256: otherDigest }, artifact: { ...inspection.artifact, sha256: otherDigest },
identity_scope: { identity_scope: {
...inspection.identity_scope, ...inspection.identity_scope,
requires_artifact_sha256: otherDigest, requires_artifact_sha256: otherDigest,
}, },
}), },
}, },
); );
const aligned = createEvidence( const aligned = createEvidence(
@@ -563,11 +565,11 @@ describe("managed call-target comparison", () => {
{ {
operation: "inspect_managed_members", operation: "inspect_managed_members",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...inspection, ...inspection,
call_edges: call_edges:
alterEdges?.(inspection.call_edges) ?? inspection.call_edges, alterEdges?.(inspection.call_edges) ?? inspection.call_edges,
}), },
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -636,10 +638,10 @@ describe("managed member comparison path workflow", () => {
{ {
operation: "inspect_managed_members", operation: "inspect_managed_members",
parameters: {}, parameters: {},
result: jsonValueSchema.parse({ result: {
...inspection, ...inspection,
coverage: { ...inspection.coverage, state: "partial" }, coverage: { ...inspection.coverage, state: "partial" },
}), },
confidence: "observed", confidence: "observed",
authority: "shipped-artifact", authority: "shipped-artifact",
}, },
@@ -0,0 +1,265 @@
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { RuleTester } from "oxlint/plugins-dev";
import { describe, it } from "vitest";
import rule from "../../scripts/lint/rules/no-evidence-json-preadmission.mjs";
const filename = resolve("src/application/RuleFixture.ts");
const imports = `
import { createEvidence, createImmutableEvidenceSteps } from "../domain/evidence.js";
import { jsonValueSchema, jsonObjectSchema } from "../domain/jsonValue.js";
`;
const evidenceSource = new URL("../../src/domain/evidence.js", import.meta.url);
const jsonSource = new URL("../../src/domain/jsonValue.js", import.meta.url);
const fileImports = `
import { createEvidence } from ${JSON.stringify(evidenceSource.href)};
import { jsonValueSchema } from ${JSON.stringify(jsonSource.href)};
`;
const absoluteImports = `
import { createEvidence } from ${JSON.stringify(fileURLToPath(evidenceSource))};
import { jsonValueSchema } from ${JSON.stringify(fileURLToPath(jsonSource))};
`;
const valid = (name: string, code: string) => ({ name, filename, code });
const invalid = (name: string, code: string, field: string) => ({
name,
filename,
code,
errors: [{ messageId: "redundantJsonAdmission", data: { field } }],
output: null,
});
RuleTester.describe = describe;
RuleTester.it = it;
new RuleTester({
languageOptions: { sourceType: "module", parserOptions: { lang: "ts" } },
}).run("no-evidence-json-preadmission", rule, {
valid: [
valid(
"file URL binding still respects lexical shadows",
`${fileImports}
function build(createEvidence) { createEvidence(target, provider, {result: jsonValueSchema.parse(input)}); }`,
),
valid(
"HTTP modules are not filesystem owners",
`
import { createEvidence } from "https://example.test/src/domain/evidence.js";
import { jsonValueSchema } from "https://example.test/src/domain/jsonValue.js";
createEvidence(target, provider, {result: jsonValueSchema.parse(input)});`,
),
valid(
"malformed file URLs do not crash owner lookup",
`
import {createEvidence} from "file://%";
import {jsonValueSchema} from ${JSON.stringify(jsonSource.href)};
createEvidence(target, provider, {result: jsonValueSchema.parse(input)});`,
),
valid(
"queried file URL does not establish the canonical owner",
`
import {createEvidence} from ${JSON.stringify(`${evidenceSource.href}?instance=other`)};
import {jsonValueSchema} from ${JSON.stringify(jsonSource.href)};
createEvidence(target, provider, {result: jsonValueSchema.parse(input)});`,
),
valid(
"fragment file URL does not establish the canonical schema",
`
import {createEvidence} from ${JSON.stringify(evidenceSource.href)};
import {jsonValueSchema} from ${JSON.stringify(`${jsonSource.href}#other`)};
createEvidence(target, provider, {result: jsonValueSchema.parse(input)});`,
),
valid(
"post-fix process capture keeps producer facts",
`${imports}
createEvidence(undefined, provider, { result: capture });`,
),
valid(
"semantic format admission remains",
`${imports}
createEvidence(target, provider, { result: processCaptureSchema.parse(capture) });`,
),
valid(
"standalone JSON admission remains",
`${imports}
const observation = { result: jsonValueSchema.parse(result) };
executeAnalysis(observation);`,
),
valid(
"strong immutable Evidence contract remains",
`${imports}
createImmutableEvidenceSteps(target, provider, { result: jsonValueSchema.parse(result) });`,
),
valid(
"digest helper owns its typed JSON input",
`${imports}
const parameters = { ...jsonObjectSchema.parse(scenario), digest: scenarioDigest(scenario) };
createEvidence(target, provider, { parameters });`,
),
valid(
"multiuse parsed value retains its separate contract",
`${imports}
const result = jsonValueSchema.parse(input);
consumeJson(result);
createEvidence(target, provider, { result });`,
),
valid(
"single-use typed local is outside the direct expression rule",
`${imports}
const result: JsonValue = jsonValueSchema.parse(input);
createEvidence(target, provider, { result });`,
),
valid(
"local creator shadows imported creator",
`${imports}
function build(createEvidence) {
return createEvidence(target, provider, { result: jsonValueSchema.parse(input) });
}`,
),
valid(
"local schema shadows imported schema",
`${imports}
function build(jsonValueSchema) {
return createEvidence(target, provider, { result: jsonValueSchema.parse(input) });
}`,
),
valid(
"namespace shadowing is respected",
`
import * as evidence from "../domain/evidence.js";
import * as json from "../domain/jsonValue.js";
function build(json) {
return evidence.createEvidence(target, provider, { result: json.jsonValueSchema.parse(input) });
}`,
),
valid(
"creator namespace shadowing is respected",
`
import * as evidence from "../domain/evidence.js";
import * as json from "../domain/jsonValue.js";
function build(evidence) {
return evidence.createEvidence(target, provider, { result: json.jsonValueSchema.parse(input) });
}`,
),
valid(
"same-named exports from another module are unrelated",
`
import { createEvidence } from "../other/evidence.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
createEvidence(target, provider, { result: jsonValueSchema.parse(input) });`,
),
valid(
"same-named schema from another module is unrelated",
`
import { createEvidence } from "../domain/evidence.js";
import { jsonValueSchema } from "../other/jsonValue.js";
createEvidence(target, provider, { result: jsonValueSchema.parse(input) });`,
),
valid(
"safeParse is a separate Result boundary",
`${imports}
createEvidence(target, provider, { result: jsonValueSchema.safeParse(input) });`,
),
valid(
"nested parsing is not an immediate payload admission",
`${imports}
createEvidence(target, provider, { parameters: { trace_spec: jsonValueSchema.parse(specification) } });`,
),
valid(
"later spread may replace the parsed field",
`${imports}
createEvidence(target, provider, { result: jsonValueSchema.parse(input), ...overrides });`,
),
valid(
"later duplicate field replaces the parsed field",
`${imports}
createEvidence(target, provider, { result: jsonValueSchema.parse(input), result: replacement });`,
),
valid(
"unknown computed field may replace the parsed field",
`${imports}
createEvidence(target, provider, { result: jsonValueSchema.parse(input), [field]: replacement });`,
),
valid(
"explicit parse options are outside the direct admission rule",
`${imports}
createEvidence(target, provider, { result: jsonValueSchema.parse(input, options) });`,
),
],
invalid: [
invalid(
"file URLs preserve canonical JSON ownership",
`${fileImports}
createEvidence(target, provider, {result: jsonValueSchema.parse(input)});`,
"result",
),
invalid(
"absolute paths preserve canonical JSON ownership",
`${absoluteImports}
createEvidence(target, provider, {result: jsonValueSchema.parse(input)});`,
"result",
),
invalid(
"pre-fix process capture repeats JSON admission",
`${imports}
createEvidence(undefined, PROCESS_PROVIDER, { result: jsonValueSchema.parse(capture) });`,
"result",
),
invalid(
"object parameters repeat generic admission",
`${imports}
createEvidence(target, provider, { parameters: jsonObjectSchema.parse(parameters) });`,
"parameters",
),
invalid(
"raw payload admission belongs to Evidence",
`${imports}
createEvidence(target, provider, { rawResult: jsonValueSchema.parse(raw) });`,
"rawResult",
),
invalid(
"aliased imports retain their owner",
`
import { createEvidence as record } from "../domain/evidence.js";
import { jsonValueSchema as json } from "../domain/jsonValue.js";
record(target, provider, { result: json.parse(input) });`,
"result",
),
invalid(
"namespace members retain their owner",
`
import * as evidence from "../domain/evidence.js";
import * as json from "../domain/jsonValue.js";
evidence.createEvidence(target, provider, { result: json.jsonValueSchema.parse(input) });`,
"result",
),
invalid(
"static computed members retain their owner",
`
import * as evidence from "../domain/evidence.js";
import * as json from "../domain/jsonValue.js";
evidence["createEvidence"](target, provider, { ["result"]: json["jsonValueSchema"]["parse"](input) });`,
"result",
),
invalid(
"TypeScript source specifiers resolve to the same owner",
`
import { createEvidence } from "../domain/evidence.ts";
import { jsonObjectSchema } from "../domain/jsonValue.ts";
createEvidence(target, provider, { parameters: jsonObjectSchema.parse(input) });`,
"parameters",
),
invalid(
"an earlier spread does not replace the final payload",
`${imports}
createEvidence(target, provider, { ...base, result: jsonValueSchema.parse(input) });`,
"result",
),
invalid(
"unrelated local scopes do not hide the import",
`${imports}
function unrelated(jsonValueSchema) { return jsonValueSchema.parse(input); }
createEvidence(target, provider, { result: jsonValueSchema.parse(input) });`,
"result",
),
],
});
@@ -0,0 +1,58 @@
import { describe, it } from "vitest";
import { fileURLToPath } from "node:url";
import { RuleTester } from "oxlint/plugins-dev";
import rule from "../../scripts/lint/rules/no-fixture-root-cleanup.mjs";
RuleTester.describe = describe;
RuleTester.it = it;
const tester = new RuleTester({
languageOptions: { parserOptions: { lang: "ts" } },
});
const filename = fileURLToPath(
new URL("../boundary/ruleControl.test.ts", import.meta.url),
);
const imports = `
import { createTestTempDirectory as temp } from "../fixtures/temporaryDirectory.js";
import { afterEach as cleanup, afterAll, onTestFinished } from "vitest";
import { rm as remove } from "node:fs/promises";
`;
const fixtureSource = new URL(
"../fixtures/temporaryDirectory.js",
import.meta.url,
);
const localCleanupImports = `import {afterEach} from "vitest"; import {rm} from "node:fs/promises";`;
tester.run("no-fixture-root-cleanup", rule, {
valid: [
`import {createTestTempDirectory as temp} from ${JSON.stringify(fixtureSource.href)}; ${localCleanupImports} function work(temp: Function) { const root = temp(); afterEach(() => rm(root)); }`,
`import {createTestTempDirectory as temp} from "https://example.test/tests/fixtures/temporaryDirectory.js"; ${localCleanupImports} const root = await temp("owned-"); afterEach(() => rm(root));`,
`import {createTestTempDirectory as temp} from ${JSON.stringify(`${fixtureSource.href}?other`)}; ${localCleanupImports} const root = await temp("owned-"); afterEach(() => rm(root));`,
`${imports} const root = await temp("owned-"); await remove(root, { recursive: true });`,
`${imports} const root = await temp("owned-"); cleanup(() => remove(join(root, "child"), {recursive: true}));`,
`${imports} const root = await otherOwner(); cleanup(() => remove(root, { recursive: true }));`,
`${imports} const root = await temp("owned-"); cleanup(async () => { const root = await otherOwner(); await remove(root, {recursive: true}); });`,
`${imports} function work(temp: Function) { const root = temp(); cleanup(() => remove(root)); }`,
`${imports} const root = await temp("owned-"); function unrelated(cleanup: Function) { cleanup(() => remove(root)); }`,
`${imports} const root = await temp("owned-"); cleanup(() => { function remove(root: string) {} remove(root); });`,
`${imports} const roots = []; const root = await temp("owned-"); roots.push(root); roots.push(await createIdaTarget()); cleanup(async () => { for (const owned of roots) await remove(owned); });`,
`${imports} let root = await temp("owned-"); root = await otherOwner(); cleanup(() => remove(root));`,
`${imports} const root = await temp("owned-"); cleanup(() => connection.close());`,
`${imports} import * as fs from "node:fs/promises"; const root = await temp("owned-"); cleanup((fs: {rm: Function}) => fs.rm(root));`,
`${imports} const root = await temp("owned-"); { const remove = otherOwner; cleanup(() => remove(root)); }`,
'import { createTestTempDirectory as temp } from "./unrelated.js"; import { afterEach } from "vitest"; import { rm } from "node:fs/promises"; const root = await temp(); afterEach(() => rm(root));',
`${imports} const root = await temp("owned-"); function callback(root: string) { return remove(root); } cleanup(callback);`,
].map((code) => ({ code, filename })),
invalid: [
`import {createTestTempDirectory as temp} from ${JSON.stringify(fixtureSource.href)}; ${localCleanupImports} const root = await temp("owned-"); afterEach(() => rm(root));`,
`import {createTestTempDirectory as temp} from ${JSON.stringify(fileURLToPath(fixtureSource))}; ${localCleanupImports} const root = await temp("owned-"); afterEach(() => rm(root));`,
`${imports} const root = await temp("owned-"); cleanup(() => remove(root, {recursive: true}));`,
`${imports} const root = await temp("owned-"); afterAll(async () => { await remove(root, {recursive: true}); });`,
`${imports} const root = await temp("owned-"); onTestFinished(() => remove(root, {recursive: true}));`,
`${imports} const root = await temp("owned-"); const alias = root; cleanup(() => remove(alias));`,
`${imports} const root = await temp("owned-"); async function release() { await remove(root); } cleanup(release);`,
`${imports} const root = await temp("owned-"); const release = () => remove(root); cleanup(release);`,
`${imports} import { rmSync } from "node:fs"; const root = await temp("owned-"); cleanup(() => rmSync(root, {recursive: true}));`,
`${imports} import { removeTestWorkspace as release } from "../support/workspace/workspaceFixture.js"; const root = await temp("owned-"); cleanup(() => release(root));`,
`${imports} const root = await temp("owned-"); cleanup(async () => { await connection.close(); await remove(root, {recursive: true}); });`,
'import * as fixture from "../fixtures/temporaryDirectory.ts"; import * as vitest from "vitest"; import * as fs from "node:fs/promises"; const root = await fixture.createTestTempDirectory("owned-"); vitest.afterEach(() => fs.rm(root, {recursive: true}));',
].map((code) => ({ code, filename, errors: [{ messageId: "duplicate" }] })),
});
@@ -0,0 +1,184 @@
import { describe, expect, it } from "vitest";
import { fileURLToPath } from "node:url";
import { RuleTester } from "oxlint/plugins-dev";
import rule from "../../scripts/lint/rules/no-positional-tool-contract.mjs";
import { collectToolCatalogSources } from "../../scripts/lint/rules/tool-catalog-sources.mjs";
RuleTester.describe = describe;
RuleTester.it = it;
const tester = new RuleTester({
languageOptions: { parserOptions: { lang: "ts" } },
});
const filename = fileURLToPath(
new URL("../../src/server/ruleControl.ts", import.meta.url),
);
const catalog =
'import { GPU_TOOL_CONTRACTS as contracts } from "../contracts/gpu/gpuToolContracts.js";';
const catalogSource = new URL(
"../../src/contracts/gpu/gpuToolContracts.js",
import.meta.url,
);
tester.run("no-positional-tool-contract", rule, {
valid: [
`import {GPU_TOOL_CONTRACTS as contracts} from ${JSON.stringify(catalogSource.href)}; function read(contracts: {name: string}[]) { return contracts[0].name; }`,
'import {GPU_TOOL_CONTRACTS as contracts} from "https://example.test/src/contracts/gpu/gpuToolContracts.js"; contracts[0].name;',
`import {GPU_TOOL_CONTRACTS as contracts} from ${JSON.stringify(`${catalogSource.href}#other`)}; contracts[0].name;`,
`${catalog} for (const contract of contracts) register(contract);`,
`${catalog} contracts.forEach(register);`,
`${catalog} const selected = contracts.find(contract => contract.name === "inspect_gpu_code_objects");`,
`${catalog} type Contract = (typeof contracts)[number];`,
`${catalog} const copies = [...contracts];`,
`${catalog} const [...copies] = contracts;`,
`${catalog} function read(contracts: {name: string}[]) { return contracts[0].name; }`,
`${catalog} function read() { const contracts = [{name: "local"}]; return contracts.at(0)?.name; }`,
'import { GPU_TOOL_CONTRACTS as contracts } from "../unrelated.js"; contracts[0].name;',
'import { unrelated as contracts } from "../contracts/gpu/gpuToolContracts.js"; contracts[0].name;',
'const TOOL_CONTRACTS = [{name: "local"}]; TOOL_CONTRACTS[0].name;',
`${catalog} const mutable = [...contracts]; mutable[0].inputSchema;`,
`${catalog} contracts.length;`,
`${catalog} contracts[0]; contracts.at(1); const [unused] = contracts;`,
`${catalog} expect(contracts[0]?.description).toContain("documented behavior");`,
`${catalog} const [first] = contracts; const toggled = contracts.map(contract => contract === first ? {...contract, returnsTargetContent: !contract.returnsTargetContent} : contract); createCatalogDigests(toggled);`,
`${catalog} const selected = contracts[0]; const alias = selected; display(alias.description);`,
`${catalog} const selected = contracts[0]; selected.name = "catalog-control";`,
`${catalog} const [selected] = contracts; const { description } = selected; display(description);`,
`${catalog} function toolRegistrationOptions(contract: unknown) {} toolRegistrationOptions(contracts[0]);`,
`${catalog} { const contracts = [{name: "local"}]; contracts[0].name; }`,
'import * as catalogs from "../contracts/gpu/gpuToolContracts.js"; function read(catalogs: {GPU_TOOL_CONTRACTS: {name: string}[]}) { return catalogs.GPU_TOOL_CONTRACTS[0].name; }',
'import * as catalogs from "../contracts/gpu/gpuToolContracts.js"; catalogs.other[0].name;',
].map((code) => ({ code, filename })),
invalid: [
`import {GPU_TOOL_CONTRACTS as contracts} from ${JSON.stringify(catalogSource.href)}; contracts[0].name;`,
`import {GPU_TOOL_CONTRACTS as contracts} from ${JSON.stringify(fileURLToPath(catalogSource))}; contracts[0].inputSchema;`,
`${catalog} import {toolRegistrationOptions as options} from ${JSON.stringify(new URL("../../src/server/toolRegistrationOptions.js", import.meta.url).href)}; options(contracts[0]);`,
`${catalog} register(contracts[0].name);`,
`${catalog} contracts["0"].inputSchema.parse(input);`,
`${catalog} contracts.at(-1)?.outputSchema;`,
`${catalog} contracts["at"](1)?.name;`,
`${catalog} const alias = contracts; alias[2].name;`,
`${catalog} const first = contracts; const second = first; second[0].outputSchema;`,
`${catalog} type Selected = (typeof contracts)[0];`,
`${catalog} const [first] = contracts; register(first.name);`,
`${catalog} const [, second] = contracts; const selected = second; selected.inputSchema;`,
`${catalog} const selected = contracts[0]; const alias = selected; alias.inputSchema;`,
`${catalog} const { inputSchema } = contracts[0];`,
`${catalog} import {toolRegistrationOptions as options} from "./toolRegistrationOptions.js"; options(contracts[0]);`,
`${catalog} import {applicationToolRegistrationOptions as options} from "./toolRegistrationOptions.ts"; const selected = contracts.at(0); options(selected);`,
`${catalog} const selected = contracts[0]; selected.name; selected.outputSchema;`,
'import * as catalogs from "../contracts/gpu/gpuToolContracts.js"; catalogs.GPU_TOOL_CONTRACTS[0].name;',
'import * as catalogs from "../contracts/gpu/gpuToolContracts.js"; type Selected = (typeof catalogs.GPU_TOOL_CONTRACTS)[0];',
'import { TOOL_CONTRACTS } from "../contracts/toolContracts.ts"; TOOL_CONTRACTS[0].name;',
'import { MODEL_TOOL_CONTRACTS as selected } from "../contracts/model/modelToolContracts.js"; selected[0].inputSchema;',
'import { IL2CPP_TOOL_CONTRACTS as selected } from "../contracts/il2cpp/il2cppToolContracts.js"; selected[0].outputSchema;',
'import { BROWSER_SCENARIO_TOOL_CONTRACTS as nested } from "../contracts/browserScenarioToolContracts.js"; nested[0].name;',
'import { FUNCTION_WORKFLOW_TOOL_CONTRACTS as nested } from "../contracts/functionWorkflowToolContracts.js"; nested.at(0)?.name;',
].map((code) => ({ code, filename, errors: [{ messageId: "positional" }] })),
});
const sourcePath = (name: string) =>
fileURLToPath(new URL(`./catalog-rule-fixture/${name}`, import.meta.url));
describe("canonical catalog source composition", () => {
it("discovers newly composed exports and nested groups without evaluating modules", () => {
const sources = new Map([
[
sourcePath("toolContracts.ts"),
`
import { NEW_CATALOG as renamed } from "./newCatalog.js";
import { GROUP } from "./group.js";
import { UNUSED } from "./unused.js";
const generated = buildContracts();
const local = [...renamed, ...GROUP, ...generated, ...UNUSED.map(makeContract)];
const alias = local;
export const TOOL_CONTRACTS = [...alias] as const;
`,
],
[
sourcePath("newCatalog.ts"),
`
throw new Error("Must never execute the runtime catalog");
export const NEW_CATALOG = [{name: "new_tool"}];
`,
],
[
sourcePath("group.ts"),
`
import { NESTED as alias } from "./nested.ts";
export const GROUP = [...alias] satisfies readonly unknown[];
`,
],
[
sourcePath("nested.ts"),
'export const NESTED = [{name: "nested_tool"}];',
],
]);
const actual = collectToolCatalogSources(
sourcePath("toolContracts.ts"),
(path) => {
const source = sources.get(path);
if (source === undefined)
throw new Error(`Unexpected source read: ${path}`);
return source;
},
);
expect(actual).toEqual(
new Map([
[sourcePath("toolContracts"), new Set(["TOOL_CONTRACTS"])],
[sourcePath("newCatalog"), new Set(["NEW_CATALOG"])],
[sourcePath("group"), new Set(["GROUP"])],
[sourcePath("nested"), new Set(["NESTED"])],
]),
);
});
it("accepts official computed definitions while discovering imported catalog groups", () => {
const actual = collectToolCatalogSources(
fileURLToPath(
new URL("../../src/contracts/toolContracts.ts", import.meta.url),
),
);
const official = fileURLToPath(
new URL("../../src/contracts/officialToolContracts", import.meta.url),
);
const definitions = fileURLToPath(
new URL("../../src/contracts/hopperMemoryContracts", import.meta.url),
);
const nested = fileURLToPath(
new URL(
"../../src/contracts/browserScenarioToolContracts",
import.meta.url,
),
);
expect(actual.get(official)).toEqual(new Set(["OFFICIAL_TOOL_CONTRACTS"]));
expect(actual.has(definitions)).toBe(false);
expect(actual.get(nested)).toEqual(
new Set(["BROWSER_SCENARIO_TOOL_CONTRACTS"]),
);
});
it("propagates source-read and parse failures instead of disabling enforcement", () => {
const failure = new Error("catalog source unavailable");
expect(() =>
collectToolCatalogSources(sourcePath("toolContracts.ts"), () => {
throw failure;
}),
).toThrow(failure);
expect(() =>
collectToolCatalogSources(
sourcePath("toolContracts.ts"),
() => "export const TOOL_CONTRACTS = [",
),
).toThrow();
});
it.each([
"export const TOOL_CONTRACTS = buildCatalog();",
"export const OTHER = [];",
"const alias = alias; export const TOOL_CONTRACTS = [...alias];",
"export const TOOL_CONTRACTS = [...unresolved];",
])("refuses unsupported or missing canonical composition: %s", (source) => {
expect(() =>
collectToolCatalogSources(sourcePath("toolContracts.ts"), () => source),
).toThrow();
});
});
+2 -3
View File
@@ -1,6 +1,5 @@
import { artifactInventoryResultSchema } from "../../src/domain/artifactGraph.js"; import { artifactInventoryResultSchema } from "../../src/domain/artifactGraph.js";
import { createEvidence, type Evidence } from "../../src/domain/evidence.js"; import { createEvidence, type Evidence } from "../../src/domain/evidence.js";
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
/** Partition a real scan into authenticated partial observations for merge tests. */ /** Partition a real scan into authenticated partial observations for merge tests. */
export const fragmentInventoryEvidence = ( export const fragmentInventoryEvidence = (
@@ -26,7 +25,7 @@ export const fragmentInventoryEvidence = (
parameters: evidence.parameters, parameters: evidence.parameters,
confidence: "derived", confidence: "derived",
authority: evidence.authority, authority: evidence.authority,
result: jsonValueSchema.parse({ result: {
...inventory, ...inventory,
nodes: inventory.nodes.filter( nodes: inventory.nodes.filter(
(_, ordinal) => ordinal % count === index, (_, ordinal) => ordinal % count === index,
@@ -37,7 +36,7 @@ export const fragmentInventoryEvidence = (
edges: inventory.edges.filter( edges: inventory.edges.filter(
(_, ordinal) => ordinal % count === index, (_, ordinal) => ordinal % count === index,
), ),
}), },
}, },
), ),
); );
+3 -3
View File
@@ -1,5 +1,5 @@
import { createEvidence, type Evidence } from "../../src/domain/evidence.js"; import { createEvidence, type Evidence } from "../../src/domain/evidence.js";
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
import { FUNCTION_COMPARISON_EXAMPLE } from "../../src/contracts/functionComparisonExample.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "../../src/contracts/functionComparisonExample.js";
import type { NativeSymbolMetadata } from "../../src/domain/native/nativeSymbol.js"; import type { NativeSymbolMetadata } from "../../src/domain/native/nativeSymbol.js";
import { functionDossierSchema } from "../../src/domain/hopperValues.js"; import { functionDossierSchema } from "../../src/domain/hopperValues.js";
@@ -24,7 +24,7 @@ export const nativeMatchingInventory = (
createEvidence(nativeMatchingTarget(side), nativeMatchingProvider, { createEvidence(nativeMatchingTarget(side), nativeMatchingProvider, {
operation: "list_procedures", operation: "list_procedures",
parameters: {}, parameters: {},
result: jsonValueSchema.parse(entries), result: entries,
evidenceLinks: [...evidenceLinks], evidenceLinks: [...evidenceLinks],
}); });
/** Explicit imported-origin fixture for tests that select exact-symbol correspondence. */ /** Explicit imported-origin fixture for tests that select exact-symbol correspondence. */
@@ -97,7 +97,7 @@ export const nativeMatchingDetails = (
observation = createEvidence(target, nativeMatchingProvider, { observation = createEvidence(target, nativeMatchingProvider, {
operation: "analyze_function", operation: "analyze_function",
parameters: { procedure: address }, parameters: { procedure: address },
result: jsonValueSchema.parse(value), result: value,
relationshipCoverage: { status: "complete" }, relationshipCoverage: { status: "complete" },
}); });
} else } else
+39
View File
@@ -0,0 +1,39 @@
import { readFile, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";
const repository = fileURLToPath(new URL("../../", import.meta.url));
const configurationSchema = z.looseObject({
jsPlugins: z
.array(z.union([z.string(), z.looseObject({ specifier: z.string() })]))
.optional(),
});
/** Preserve repository lint policy while resolving local plugins at their owner. */
export const writeRepositoryOxlintConfiguration = async (
project: string,
): Promise<string> => {
const configuration = configurationSchema.parse(
JSON.parse(await readFile(join(repository, ".oxlintrc.json"), "utf8")),
);
const rebase = (specifier: string): string =>
specifier.startsWith(".") ? resolve(repository, specifier) : specifier;
const path = join(project, ".oxlintrc.json");
await writeFile(
path,
JSON.stringify({
...configuration,
...(configuration.jsPlugins === undefined
? {}
: {
jsPlugins: configuration.jsPlugins.map((plugin) =>
typeof plugin === "string"
? rebase(plugin)
: { ...plugin, specifier: rebase(plugin.specifier) },
),
}),
}),
);
return path;
};
+2 -2
View File
@@ -1,7 +1,7 @@
import { readyReviewedLedgerRequest } from "../../src/application/ReconstructionObligationLedger.fixture.js"; import { readyReviewedLedgerRequest } from "../../src/application/ReconstructionObligationLedger.fixture.js";
import { createEvidence } from "../../src/domain/evidence.js"; import { createEvidence } from "../../src/domain/evidence.js";
import { createEvidenceBundle } from "../../src/domain/evidenceBundle.js"; import { createEvidenceBundle } from "../../src/domain/evidenceBundle.js";
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
import { reconstructionObligationProofSchema } from "../../src/domain/reconstructionObligationProof.js"; import { reconstructionObligationProofSchema } from "../../src/domain/reconstructionObligationProof.js";
import type { ReconstructionObligationLedgerInput } from "../../src/domain/reconstructionObligationLedgerSchemas.js"; import type { ReconstructionObligationLedgerInput } from "../../src/domain/reconstructionObligationLedgerSchemas.js";
@@ -16,7 +16,7 @@ const replaceProof = (
predicateType: original.predicate_type, predicateType: original.predicate_type,
operation: original.operation, operation: original.operation,
parameters: original.parameters, parameters: original.parameters,
result: jsonValueSchema.parse(result), result: result,
confidence: original.confidence, confidence: original.confidence,
authority: original.authority, authority: original.authority,
}); });
+1 -2
View File
@@ -5,7 +5,6 @@ import { browserScenarioCaptureSchema } from "../../src/domain/browserScenarioCa
import { DEFAULT_BROWSER_SCENARIO_CAPTURE_LIMITS } from "../../src/domain/browserScenarioCaptureLimits.js"; import { DEFAULT_BROWSER_SCENARIO_CAPTURE_LIMITS } from "../../src/domain/browserScenarioCaptureLimits.js";
import { browserScenarioEventSchema } from "../../src/domain/browserScenarioCaptureValues.js"; import { browserScenarioEventSchema } from "../../src/domain/browserScenarioCaptureValues.js";
import { createEvidence } from "../../src/domain/evidence.js"; import { createEvidence } from "../../src/domain/evidence.js";
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
/** Source-owned script capture with independently authored bytes. */ /** Source-owned script capture with independently authored bytes. */
export const scriptScenarioFixture = ( export const scriptScenarioFixture = (
@@ -134,7 +133,7 @@ export const scriptCaptureEvidenceFixture = () =>
{ id: "source-fixture", name: "Source fixture", version: "1" }, { id: "source-fixture", name: "Source fixture", version: "1" },
{ {
operation: "capture_browser_scenario", operation: "capture_browser_scenario",
result: jsonValueSchema.parse(scriptScenarioFixture()), result: scriptScenarioFixture(),
parameters: {}, parameters: {},
confidence: "observed", confidence: "observed",
authority: "controlled-replay", authority: "controlled-replay",