chore(lint): guard validation ownership and asynchronous assertions
This commit is contained in:
1 parent
ed86f9c3bb
commit
76afdd35ae
69 files changed
+2384
-190
No files matched your search
+68
-4
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript", "import", "unicorn", "oxc"],
|
||||
"jsPlugins": [{ "name": "rea", "specifier": "./scripts/lint/index.mjs" }],
|
||||
"plugins": ["typescript", "import", "unicorn", "oxc", "vitest"],
|
||||
"rules": {
|
||||
"rea/no-buffer-deep-equality": "error",
|
||||
"rea/cancellation-cause": "error",
|
||||
@@ -29,7 +28,37 @@
|
||||
"max-params": ["warn", 4],
|
||||
"no-var": "warn",
|
||||
"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": [
|
||||
{
|
||||
@@ -42,6 +71,12 @@
|
||||
"no-var-requires": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/**/*.ts"],
|
||||
"rules": {
|
||||
"typescript/no-misused-promises": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
|
||||
"rules": {
|
||||
@@ -53,7 +88,8 @@
|
||||
"skipBlankLines": 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": {
|
||||
@@ -115,5 +170,14 @@
|
||||
".devin",
|
||||
".factory",
|
||||
"third_party"
|
||||
],
|
||||
"options": {
|
||||
"reportUnusedDisableDirectives": "error"
|
||||
},
|
||||
"jsPlugins": [
|
||||
{
|
||||
"name": "rea",
|
||||
"specifier": "./scripts/lint/index.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
+25
-7
@@ -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
|
||||
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
|
||||
script scan exceeded the bounded process-family memory envelope, including
|
||||
with Go runtime limits. Future scoped evaluation can consider
|
||||
`typescript/no-floating-promises`, `typescript/return-await` with
|
||||
`error-handling-correctness-only`, `typescript/no-misused-promises`, and
|
||||
`typescript/switch-exhaustiveness-check`. None replaces behavioral checks for
|
||||
source identity, omission versus unknown, or Evidence provenance.
|
||||
`rea/no-evidence-json-preadmission` rejects direct generic JSON parses in
|
||||
`createEvidence` payload expressions; it does not analyze intermediate values.
|
||||
`rea/no-positional-tool-contract` rejects catalog indices used to select
|
||||
operation names, schemas or types; display-only selection remains allowed.
|
||||
`rea/no-fixture-root-cleanup` follows `const` fixture-root aliases into teardown
|
||||
deletion; mutable bindings and mixed root arrays remain outside its scope.
|
||||
|
||||
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.
|
||||
Before handing off a PR, run `npm run check` and generated-document checks when
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@ REA npm dependency inventory
|
||||
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
|
||||
License identifiers are upstream lockfile declarations, not a legal assessment.
|
||||
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.
|
||||
|
||||
@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
|
||||
Archive: https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.72.0.tgz
|
||||
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
|
||||
Lockfile path: node_modules/@oxlint/binding-android-arm-eabi
|
||||
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
|
||||
Archive: https://registry.npmjs.org/oxlint/-/oxlint-1.87.0.tgz
|
||||
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
|
||||
Lockfile path: node_modules/p-limit
|
||||
Package: https://www.npmjs.com/package/p-limit/v/2.3.0
|
||||
|
||||
Generated
+103
@@ -73,6 +73,7 @@
|
||||
"lint-staged": "16.4.0",
|
||||
"oxfmt": "0.72.0",
|
||||
"oxlint": "1.87.0",
|
||||
"oxlint-tsgolint": "7.0.2005",
|
||||
"release-please": "17.3.0",
|
||||
"rolldown": "1.2.12",
|
||||
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
|
||||
@@ -3491,6 +3492,90 @@
|
||||
"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": {
|
||||
"version": "1.87.0",
|
||||
"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": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
|
||||
+7
-4
@@ -112,7 +112,7 @@
|
||||
"prebuild": "npm run deps:check",
|
||||
"build": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills --force",
|
||||
"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",
|
||||
"check:fast": "node scripts/run-turbo.mjs run typecheck lint --output-logs=errors-only",
|
||||
"precheck:changed": "npm run deps:check",
|
||||
@@ -120,14 +120,14 @@
|
||||
"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",
|
||||
"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:fast": "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: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: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: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",
|
||||
@@ -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",
|
||||
"verify:web:qlog": "npm run build:cached && node scripts/verify-qlog-captures.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": {
|
||||
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [
|
||||
@@ -404,6 +406,7 @@
|
||||
"lint-staged": "16.4.0",
|
||||
"oxfmt": "0.72.0",
|
||||
"oxlint": "1.87.0",
|
||||
"oxlint-tsgolint": "7.0.2005",
|
||||
"release-please": "17.3.0",
|
||||
"rolldown": "1.2.12",
|
||||
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ function classifyPackage(base, head) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip$|jscpd$)/.test(
|
||||
/^(test(?::|$)|check(?::|$)|lint(?::|$)|format(?::|$)|knip(?::|$)|jscpd$)/.test(
|
||||
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();
|
||||
@@ -48,7 +48,12 @@ const duplicateReport = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const run = (packageName, entrypoint, arguments_) => {
|
||||
const run = (
|
||||
packageName,
|
||||
entrypoint,
|
||||
arguments_,
|
||||
environment = process.env,
|
||||
) => {
|
||||
const executable = join(
|
||||
dirname(require.resolve(`${packageName}/package.json`)),
|
||||
entrypoint,
|
||||
@@ -56,6 +61,7 @@ const run = (packageName, entrypoint, arguments_) => {
|
||||
const result = spawnSync(process.execPath, [executable, ...arguments_], {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
env: environment,
|
||||
});
|
||||
if (result.stderr) process.stderr.write(result.stderr);
|
||||
if (result.error !== undefined) throw result.error;
|
||||
@@ -66,14 +72,30 @@ const run = (packageName, entrypoint, arguments_) => {
|
||||
|
||||
try {
|
||||
await mkdir(reportRoot, { recursive: true });
|
||||
if (mode === "lint") {
|
||||
const reportPath = join(reportRoot, "oxlint.json");
|
||||
if (mode === "lint" || mode === "typed-lint") {
|
||||
const typed = mode === "typed-lint";
|
||||
const reportPath = join(
|
||||
reportRoot,
|
||||
typed ? "typed-oxlint.json" : "oxlint.json",
|
||||
);
|
||||
await rm(reportPath, { force: true });
|
||||
const result = run("oxlint", "bin/oxlint", [
|
||||
...extraArguments,
|
||||
"--format",
|
||||
"json",
|
||||
]);
|
||||
const result = run(
|
||||
"oxlint",
|
||||
"bin/oxlint",
|
||||
[
|
||||
...(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;
|
||||
try {
|
||||
report = lintReport.parse(JSON.parse(result.stdout));
|
||||
@@ -89,7 +111,7 @@ try {
|
||||
({ severity }) => severity === "error",
|
||||
);
|
||||
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) {
|
||||
const location = diagnostic.labels[0]?.span;
|
||||
@@ -106,7 +128,9 @@ try {
|
||||
)
|
||||
.slice(0, 8))
|
||||
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;
|
||||
} else if (mode === "duplicates") {
|
||||
const output = join(reportRoot, "jscpd");
|
||||
@@ -143,7 +167,7 @@ try {
|
||||
console.log(`Full diagnostics: ${reportPath}; npm run jscpd:details`);
|
||||
process.exitCode = result.status;
|
||||
} else {
|
||||
throw new Error("Expected lint or duplicates");
|
||||
throw new Error("Expected lint, typed-lint or duplicates");
|
||||
}
|
||||
} catch (cause) {
|
||||
console.error(cause instanceof Error ? cause.message : String(cause));
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import noBufferDeepEquality from "./no-buffer-deep-equality.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 {
|
||||
meta: { name: "rea" },
|
||||
rules: {
|
||||
"no-buffer-deep-equality": noBufferDeepEquality,
|
||||
"cancellation-cause": cancellationCause,
|
||||
"no-evidence-json-preadmission": noEvidenceJsonPreadmission,
|
||||
"no-fixture-root-cleanup": noFixtureRootCleanup,
|
||||
"no-positional-tool-contract": noPositionalToolContract,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export declare const resolveImportSource: (
|
||||
filename: string,
|
||||
specifier: unknown,
|
||||
) => string | undefined;
|
||||
@@ -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>>;
|
||||
@@ -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 { createEvidence } from "../domain/evidence.js";
|
||||
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { javascriptApplicationAnalysisResultSchema } from "../domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { artifactEvidence } from "../domain/javascript/javascriptApplicationGraph.fixture.js";
|
||||
import {
|
||||
@@ -108,7 +107,7 @@ const semanticApplication = (
|
||||
predicateType: example.predicate_type,
|
||||
operation: example.operation,
|
||||
parameters: example.parameters,
|
||||
result: jsonValueSchema.parse({ ...base, semantic_graph: graph }),
|
||||
result: { ...base, semantic_graph: graph },
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
createJavaScriptApplicationGraph,
|
||||
createJavaScriptApplicationNode,
|
||||
} from "../domain/javascript/javascriptApplicationGraph.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { MANAGED_WORKFLOW_PROVIDER } from "../domain/investigationProviders.js";
|
||||
import { createProcessCaptureEvidence } from "./process/ProcessEvidence.js";
|
||||
|
||||
@@ -90,7 +89,7 @@ const applicationWithUnknowns = (
|
||||
predicateType: "rea.managed-application-graph",
|
||||
operation: "project_managed_application_graph",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
projection_id: `magp_${"a".repeat(64)}`,
|
||||
root_artifact_sha256: APPLICATION_GRAPH_DIGESTS.asar,
|
||||
source_evidence: {
|
||||
@@ -113,7 +112,7 @@ const applicationWithUnknowns = (
|
||||
coverage: { status: "complete-within-inputs" },
|
||||
evidence_links: [source.evidence_id],
|
||||
limitations: [],
|
||||
}),
|
||||
},
|
||||
confidence: "inferred",
|
||||
authority: "analyst-inference",
|
||||
evidenceLinks: [source.evidence_id],
|
||||
@@ -212,7 +211,7 @@ describe("reconstruction obligation candidates", () => {
|
||||
predicateType: "rea.process-capture",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...rest,
|
||||
manifest,
|
||||
exit: {
|
||||
@@ -224,7 +223,7 @@ describe("reconstruction obligation candidates", () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
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,
|
||||
operation: example.operation,
|
||||
parameters: example.parameters,
|
||||
result: jsonValueSchema.parse({ ...base, semantic_graph: semanticGraph }),
|
||||
result: { ...base, semantic_graph: semanticGraph },
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
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,
|
||||
operation: example.operation,
|
||||
parameters: example.parameters,
|
||||
result: jsonValueSchema.parse({ ...base, semantic_graph: semanticGraph }),
|
||||
result: { ...base, semantic_graph: semanticGraph },
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
evidenceLinks: [source.evidence_id],
|
||||
|
||||
@@ -9,7 +9,6 @@ import { compareArtifacts } from "../../domain/artifactComparison.js";
|
||||
import { parseArtifactInventoryEvidence } from "../../domain/artifactInventoryEvidence.js";
|
||||
import { projectAppleInventory } from "../../domain/apple/appleApplication.js";
|
||||
import { createEvidence } from "../../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
import { thinMach } from "../../domain/binaryTarget.fixture.js";
|
||||
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
||||
|
||||
@@ -32,7 +31,7 @@ const observeArchive = async (order: readonly string[]) => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventory),
|
||||
result: inventory,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -128,7 +127,7 @@ describe("content identity and occurrence facts", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventory),
|
||||
result: inventory,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { readPyzArchive } from "./PyzArchive.js";
|
||||
import { marshalCode } from "../../python/PythonPyc.fixture.js";
|
||||
import { createEvidence } from "../../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
import { parseArtifactInventoryEvidence } from "../../domain/artifactInventoryEvidence.js";
|
||||
import {
|
||||
ArtifactReaderFailure,
|
||||
@@ -365,7 +364,7 @@ it.each([true, false])(
|
||||
parameters: { page: index },
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inventory,
|
||||
nodes: index === 0 ? inventory.nodes : [],
|
||||
edges: index === 0 ? inventory.edges : [],
|
||||
@@ -378,7 +377,7 @@ it.each([true, false])(
|
||||
row,
|
||||
]
|
||||
: [row],
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -373,7 +373,6 @@ const emitDeepLink = (
|
||||
);
|
||||
|
||||
/** 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 (
|
||||
application: ElectronApplication,
|
||||
): Promise<{
|
||||
|
||||
@@ -10,7 +10,6 @@ import { AnalysisInputError } from "../analysisErrorCore.js";
|
||||
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
||||
import { digestCanonicalValue } from "../canonicalDigest.js";
|
||||
import { createEvidence, type Evidence } from "../evidence.js";
|
||||
import { jsonValueSchema } from "../jsonValue.js";
|
||||
|
||||
type EntryKind = "file" | "directory" | "symlink" | "slice";
|
||||
type RootFormat = "ipa" | "zip" | "dmg" | "directory" | "asar";
|
||||
@@ -136,7 +135,7 @@ const inventoryEvidence = (
|
||||
parameters: {},
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
manifest: {
|
||||
manifest_id: `agm_${digestCanonicalValue(
|
||||
{ root_artifact_id: rootId, graph_sha256: graphSha256 },
|
||||
@@ -157,7 +156,7 @@ const inventoryEvidence = (
|
||||
integrity_contradictions: [],
|
||||
coverage: { scope: "container-entries", status: "complete" },
|
||||
limitations: [],
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
parseArtifactInspectionInventoryResult,
|
||||
} from "./artifactInventoryEvidence.js";
|
||||
import { createEvidence } from "./evidence.js";
|
||||
import { jsonValueSchema } from "./jsonValue.js";
|
||||
|
||||
const inventoryEvidence = ARTIFACT_COMPARISON_EXAMPLE.left;
|
||||
const target = inventoryEvidence.subject;
|
||||
@@ -31,7 +30,7 @@ const inspectionEvidence = (result: unknown, subject = target) =>
|
||||
{
|
||||
operation: "inspect_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(result),
|
||||
result,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -152,12 +151,12 @@ it("keeps a commitment failure discovered across individually incomplete pages a
|
||||
parameters: {},
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...complete,
|
||||
manifest,
|
||||
nodes: includeNodes ? complete.nodes : [],
|
||||
occurrences: includeNodes ? [] : complete.occurrences,
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
const nodes = page(true);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "./changedBehavior.js";
|
||||
import { createEvidence, type Evidence } from "./evidence.js";
|
||||
import { functionComparisonResultSchema } from "./functionComparisonSchemas.js";
|
||||
import { jsonValueSchema, type JsonValue } from "./jsonValue.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
|
||||
const source = (digit: string): Evidence =>
|
||||
createEvidence(
|
||||
@@ -350,13 +350,11 @@ describe("changed-behavior input and Evidence validation", () => {
|
||||
predicateType: base.predicate_type,
|
||||
operation: base.operation,
|
||||
parameters: base.parameters,
|
||||
result: jsonValueSchema.parse(
|
||||
functionComparisonResultSchema.parse({
|
||||
...result,
|
||||
dimensions,
|
||||
changes: dimensions.filter(({ status }) => status !== "unchanged"),
|
||||
}),
|
||||
),
|
||||
result: functionComparisonResultSchema.parse({
|
||||
...result,
|
||||
dimensions,
|
||||
changes: dimensions.filter(({ status }) => status !== "unchanged"),
|
||||
}),
|
||||
confidence: base.confidence,
|
||||
authority: base.authority,
|
||||
evidenceLinks: base.evidence_links,
|
||||
|
||||
@@ -664,7 +664,7 @@ describe("function comparison ordering controls", () => {
|
||||
parameters: enhancedInputSchemas.analyze_function.parse({
|
||||
procedure: "dispatch",
|
||||
}),
|
||||
result: jsonValueSchema.parse(result),
|
||||
result,
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
relationshipCoverage: { status: "complete" },
|
||||
|
||||
@@ -35,11 +35,9 @@ const artifactComparison = createEvidence(
|
||||
left_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.left.evidence_id],
|
||||
right_evidence_ids: [ARTIFACT_COMPARISON_EXAMPLE.right.evidence_id],
|
||||
},
|
||||
result: jsonValueSchema.parse(
|
||||
compareArtifacts(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.left,
|
||||
ARTIFACT_COMPARISON_EXAMPLE.right,
|
||||
),
|
||||
result: compareArtifacts(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.left,
|
||||
ARTIFACT_COMPARISON_EXAMPLE.right,
|
||||
),
|
||||
confidence: "derived",
|
||||
authority: "analyst-inference",
|
||||
@@ -157,10 +155,10 @@ it("keeps text, assembly and unavailable CFG caveats on their selected correlati
|
||||
{
|
||||
operation: source.operation,
|
||||
parameters: source.parameters,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...functionDossierSchema.parse(source.normalized_result),
|
||||
basic_blocks: [{ start: "0x1000", end: "0x1000", successors: [] }],
|
||||
}),
|
||||
},
|
||||
confidence: source.confidence,
|
||||
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,
|
||||
right_evidence_id: right.evidence_id,
|
||||
},
|
||||
result: jsonValueSchema.parse(computed),
|
||||
result: computed,
|
||||
confidence: "derived",
|
||||
authority: "analyst-inference",
|
||||
limitations: computed.limitations,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
parseEvidenceBundle,
|
||||
serializeEvidenceBundle,
|
||||
} from "./evidenceBundle.js";
|
||||
import { jsonValueSchema } from "./jsonValue.js";
|
||||
import {
|
||||
webMcpDiscoverySchema,
|
||||
type WebMcpDiscovery,
|
||||
@@ -106,7 +105,7 @@ describe("WebMCP discovery consequential annotation", () => {
|
||||
predicateType: "rea.webmcp-discovery",
|
||||
operation: "discover_webmcp_tools",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(result),
|
||||
result,
|
||||
});
|
||||
const bundle = createEvidenceBundle([evidence]);
|
||||
const reread = parseEvidenceBundle(
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
parseEvidence,
|
||||
type Evidence,
|
||||
} from "../../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
|
||||
@@ -75,7 +75,7 @@ const assertCoverageControls = async ({
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(result),
|
||||
result: result,
|
||||
confidence: "derived",
|
||||
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 { pathToFileURL } from "node:url";
|
||||
|
||||
@@ -145,7 +145,6 @@ it("closes an Electron inspection session when cleanup is cancelled", async () =
|
||||
});
|
||||
onTestFinished(async () => {
|
||||
await browser.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const pending = new CdpElectronProvider().inspectPage(
|
||||
@@ -204,7 +203,6 @@ it("classifies cancellation that arrives during final Electron detach", async ()
|
||||
});
|
||||
onTestFinished(async () => {
|
||||
await browser.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const result = await new CdpElectronProvider().inspectPage(
|
||||
@@ -283,7 +281,6 @@ it.each(
|
||||
closing.mockRestore();
|
||||
await provider.close();
|
||||
await browser.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
const result =
|
||||
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 { pathToFileURL } from "node:url";
|
||||
|
||||
@@ -52,7 +52,6 @@ it.each([
|
||||
| undefined;
|
||||
if (closeProvider !== undefined) await closeProvider.call(provider);
|
||||
await browser.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const close = vi
|
||||
|
||||
@@ -36,7 +36,7 @@ const observe = async (path: string) => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventory),
|
||||
result: inventory,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -62,7 +62,7 @@ const inspectPages = (
|
||||
{
|
||||
operation: "inspect_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
inspection_id: `ai_${"f".repeat(64)}`,
|
||||
subject: {
|
||||
manifest_id: inventory.manifest.manifest_id,
|
||||
@@ -96,7 +96,7 @@ const inspectPages = (
|
||||
},
|
||||
evidence_links: pages.map(({ evidence_id }) => evidence_id),
|
||||
limitations: [],
|
||||
}),
|
||||
},
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -144,7 +144,7 @@ describe("leaf artifact comparison", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(withoutCoverage),
|
||||
result: withoutCoverage,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -306,11 +306,11 @@ describe("artifact inventory coverage", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...graph,
|
||||
coverage: { scope: "root", status: "unknown" },
|
||||
limitations: ["No extra child entries were observed."],
|
||||
}),
|
||||
},
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -348,10 +348,10 @@ describe("artifact inventory coverage", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: fragment.parameters,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...fragmentGraph,
|
||||
coverage: { ...fragmentGraph.coverage, status: "unknown" },
|
||||
}),
|
||||
},
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -448,10 +448,10 @@ describe("artifact comparison", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...completeInventory,
|
||||
nodes: [],
|
||||
}),
|
||||
},
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
limitations: ["Inventory node observations are unavailable."],
|
||||
@@ -496,10 +496,10 @@ describe("artifact comparison completeness", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inventory,
|
||||
coverage: { scope: "root", status: "complete" },
|
||||
}),
|
||||
},
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -862,10 +862,10 @@ describe("artifact comparison fragment integrity", () => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...artifactInventoryResultSchema.parse(fragment.normalized_result),
|
||||
coverage: { scope: "root", status: "complete" },
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(() =>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { inventoryArtifact } from "../../fixtures/artifactInventory.js";
|
||||
import { compareArtifacts } from "../../../src/domain/artifactComparison.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const observe = async (
|
||||
@@ -30,7 +30,7 @@ const observe = async (
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventory),
|
||||
result: inventory,
|
||||
confidence: partial ? "derived" : "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import { inventoryArtifact } from "../../fixtures/artifactInventory.js";
|
||||
import { compareArtifacts } from "../../../src/domain/artifactComparison.js";
|
||||
import type { ArtifactInventoryResult } from "../../../src/domain/artifactGraph.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const observeZip = async (entries: readonly (readonly [string, string])[]) => {
|
||||
@@ -32,7 +32,7 @@ const observeZip = async (entries: readonly (readonly [string, string])[]) => {
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventory),
|
||||
result: inventory,
|
||||
confidence: "observed",
|
||||
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 () => {
|
||||
const { directory } = await fixture();
|
||||
const before = {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { reconcileJavaScriptRuntimeEvidence } from "../../../src/application/javascript/JavaScriptRuntimeReconciliationService.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { AnalysisInputError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import {
|
||||
applicationVersionComparisonResultSchema,
|
||||
@@ -377,10 +377,10 @@ describe("Application graph Evidence envelopes", () => {
|
||||
predicateType: source.predicate_type,
|
||||
operation: source.operation,
|
||||
parameters: source.parameters,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...result,
|
||||
evidence_links: [...result.evidence_links, extraLink],
|
||||
}),
|
||||
},
|
||||
rawResult: source.raw_result,
|
||||
confidence: source.confidence,
|
||||
authority: source.authority,
|
||||
@@ -479,7 +479,7 @@ describe("JavaScript application workflow error diagnostics", () => {
|
||||
predicateType: evidence.predicate_type,
|
||||
operation: evidence.operation,
|
||||
parameters: evidence.parameters,
|
||||
result: jsonValueSchema.parse(malformedResult),
|
||||
result: malformedResult,
|
||||
confidence: evidence.confidence,
|
||||
authority: evidence.authority,
|
||||
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 { 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 { fileURLToPath } from "node:url";
|
||||
import { expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { writeRepositoryOxlintConfiguration } from "../../fixtures/oxlintConfiguration.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
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 () => {
|
||||
const root = await fixture();
|
||||
for (const name of [".gitignore", ".oxlintrc.json"])
|
||||
await writeFile(join(root, name), await readFile(join(repository, name)));
|
||||
await cp(join(repository, "scripts/lint"), join(root, "scripts/lint"), {
|
||||
recursive: true,
|
||||
});
|
||||
await writeFile(
|
||||
join(root, ".gitignore"),
|
||||
await readFile(join(repository, ".gitignore")),
|
||||
);
|
||||
await writeRepositoryOxlintConfiguration(root);
|
||||
const authored = "website/public/assets/authored.js";
|
||||
const staged =
|
||||
"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.status).toBe(1);
|
||||
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 { 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 () => {
|
||||
const root = await createTestTempDirectory("rea-web-artifact-read-owner-");
|
||||
onTestFinished(async () => {
|
||||
onTestFinished(() => {
|
||||
injected.sourcePath = undefined;
|
||||
injected.abortController = undefined;
|
||||
injected.failClose = false;
|
||||
injected.closeAttempts = 0;
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
const source = 'import "./dep.js";';
|
||||
const fixture = webModuleArtifactsFixture(source);
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from "../../../src/domain/evidence.js";
|
||||
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
|
||||
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import {
|
||||
analysisViewResultSchema,
|
||||
type AnalysisViewResult,
|
||||
@@ -446,10 +446,10 @@ it.each(["full", "compact"] as const)(
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...graph,
|
||||
occurrences: graph.occurrences.slice(0, 1),
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
const partialReply = await client.callTool({
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "../../../src/domain/evidence.js";
|
||||
import { EvidenceIntegrityError } from "../../../src/domain/evidenceErrors.js";
|
||||
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
|
||||
import {
|
||||
artifactEdgeId,
|
||||
@@ -468,7 +468,7 @@ it("records an explicitly continued mismatch, preserves verified siblings, and n
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventoryWithoutCoverage),
|
||||
result: inventoryWithoutCoverage,
|
||||
},
|
||||
);
|
||||
const retainedComparison = await client.callTool({
|
||||
@@ -913,7 +913,7 @@ const withForeignMetadataAndNestedZipMember = (
|
||||
parameters: evidence.parameters,
|
||||
confidence: evidence.confidence,
|
||||
authority: evidence.authority,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inventory,
|
||||
occurrences,
|
||||
edges,
|
||||
@@ -924,7 +924,7 @@ const withForeignMetadataAndNestedZipMember = (
|
||||
graph_sha256: graphSha256,
|
||||
manifest_id: manifestId,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.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 { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
@@ -195,7 +195,7 @@ const createBundleSourceSession = () => {
|
||||
predicateType: "rea.process-capture",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE),
|
||||
result: EMPTY_PROCESS_CAPTURE_EXAMPLE,
|
||||
confidence: "observed",
|
||||
authority: "controlled-replay",
|
||||
}),
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
type FunctionComparisonResult,
|
||||
} from "../../../src/domain/functionComparisonSchemas.js";
|
||||
import { summarize } from "../../../src/domain/functionComparisonResults.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
@@ -165,7 +165,7 @@ it.each([
|
||||
predicateType: base.predicate_type,
|
||||
operation: base.operation,
|
||||
parameters: base.parameters,
|
||||
result: jsonValueSchema.parse(scenario.result(result)),
|
||||
result: scenario.result(result),
|
||||
confidence: base.confidence,
|
||||
authority: base.authority,
|
||||
limitations: base.limitations,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarnes
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
|
||||
import { compareFunctions } from "../../../src/domain/functionComparison.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
@@ -74,7 +73,7 @@ it.each([
|
||||
{
|
||||
operation: "analyze_function",
|
||||
parameters: { procedure: entry },
|
||||
result: jsonValueSchema.parse(value),
|
||||
result: value,
|
||||
confidence: "derived",
|
||||
relationshipCoverage: { status: "complete" },
|
||||
},
|
||||
@@ -532,14 +531,14 @@ it.each([
|
||||
};
|
||||
const consistent = createEvidence(undefined, provider, {
|
||||
...observation,
|
||||
result: jsonValueSchema.parse(result),
|
||||
result: result,
|
||||
});
|
||||
const inconsistent =
|
||||
status === "unchanged"
|
||||
? null
|
||||
: createEvidence(undefined, provider, {
|
||||
...observation,
|
||||
result: jsonValueSchema.parse({ ...result, status: "unchanged" }),
|
||||
result: { ...result, status: "unchanged" },
|
||||
});
|
||||
const directory = await createTestTempDirectory("rea-function-status-");
|
||||
const path = join(directory, "bundle.json");
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionComparisonResultSchema } from "../../../src/domain/functionComparisonSchemas.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { reconstructionVerificationResultSchema } from "../../../src/domain/reconstructionVerificationSchemas.js";
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
@@ -147,7 +147,7 @@ const evidence = (
|
||||
? {}
|
||||
: { analysisProfile: options.profile ?? profile }),
|
||||
parameters: { ...example.parameters, observation: id },
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...dossier,
|
||||
procedure: {
|
||||
...dossier.procedure,
|
||||
@@ -162,7 +162,7 @@ const evidence = (
|
||||
callers: [...(options.callers ?? [])],
|
||||
incoming_references: [...(options.incoming ?? [])],
|
||||
outgoing_references: [...outgoing],
|
||||
}),
|
||||
},
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
relationshipCoverage: { status: "complete" },
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
functionComparisonResultSchema,
|
||||
type FunctionComparisonResult as Comparison,
|
||||
} from "../../../src/domain/functionComparisonSchemas.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
|
||||
@@ -30,7 +30,7 @@ const evidence = (
|
||||
{
|
||||
operation: "analyze_function",
|
||||
parameters: { ...template.parameters, observation: id },
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...base,
|
||||
referenced_strings: [...strings],
|
||||
swift_string_literals:
|
||||
@@ -47,7 +47,7 @@ const evidence = (
|
||||
},
|
||||
],
|
||||
swift_string_literal_omissions: [],
|
||||
}),
|
||||
},
|
||||
confidence: template.confidence,
|
||||
authority: template.authority,
|
||||
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 { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { expect, it } from "vitest";
|
||||
@@ -14,18 +14,14 @@ import {
|
||||
} from "../../../src/application/AnalysisProvider.js";
|
||||
import type { BinarySession } from "../../../src/application/binary/BinarySession.js";
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { INVESTIGATION_EXAMPLES } from "../../../src/contracts/investigationExamples.js";
|
||||
import {
|
||||
INVESTIGATION_EXAMPLES,
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
PROCESS_CAPTURE_RECONSTRUCTION,
|
||||
PROCESS_CAPTURE_REFERENCE,
|
||||
PROCESS_COMPARISON_EVIDENCE,
|
||||
} from "../../../src/contracts/investigationExamples.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import {
|
||||
jsonObjectSchema,
|
||||
jsonValueSchema,
|
||||
} from "../../../src/domain/jsonValue.js";
|
||||
import { jsonObjectSchema } from "../../../src/domain/jsonValue.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { observed } from "../../fixtures/analysisExecution.js";
|
||||
import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js";
|
||||
@@ -217,7 +213,7 @@ const dossierWithCallees = (addresses: readonly string[]) => {
|
||||
predicateType: base.predicate_type,
|
||||
operation: base.operation,
|
||||
parameters: base.parameters,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...jsonObjectSchema.parse(base.normalized_result),
|
||||
callees: addresses.map((address) => ({
|
||||
address,
|
||||
@@ -228,7 +224,7 @@ const dossierWithCallees = (addresses: readonly string[]) => {
|
||||
reason: "The fixture has no callee body-extent observation.",
|
||||
},
|
||||
})),
|
||||
}),
|
||||
},
|
||||
rawResult: base.raw_result,
|
||||
confidence: base.confidence,
|
||||
authority: base.authority,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE,
|
||||
} from "../../../src/contracts/managed/managedWorkflowExamples.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import {
|
||||
managedMemberInspectionSchema,
|
||||
managedNativeBoundaryInspectionSchema,
|
||||
@@ -34,7 +34,7 @@ const emptyExportEvidence = () => {
|
||||
{
|
||||
operation: "inspect_macho",
|
||||
parameters: original.parameters,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...result,
|
||||
exports: {
|
||||
items: [],
|
||||
@@ -42,7 +42,7 @@ const emptyExportEvidence = () => {
|
||||
exhaustive: true,
|
||||
limitations: [],
|
||||
},
|
||||
}),
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: [],
|
||||
},
|
||||
@@ -63,7 +63,7 @@ const secondDeclaration = () => {
|
||||
return createEvidence(undefined, original.provider, {
|
||||
operation: "inspect_managed_native_boundaries",
|
||||
parameters: original.parameters,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...result,
|
||||
pinvoke_imports: [
|
||||
{
|
||||
@@ -74,7 +74,7 @@ const secondDeclaration = () => {
|
||||
import_name: "open_native_again",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: original.limitations,
|
||||
});
|
||||
@@ -128,7 +128,7 @@ const memberEvidence = (
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: original.parameters,
|
||||
result: jsonValueSchema.parse(normalized),
|
||||
result: normalized,
|
||||
rawResult: null,
|
||||
limitations: normalized.limitations,
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
parseEvidence,
|
||||
type EvidenceProvider,
|
||||
} from "../../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { digestProcessCommitment } from "../../../src/domain/process/processScenario.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
@@ -45,14 +45,14 @@ const captureEvidence = (
|
||||
predicateType: "rea.process-capture",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: { side },
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...observation,
|
||||
manifest: {
|
||||
...observation.manifest,
|
||||
comparison_contract: comparisonContract,
|
||||
comparison_contract_sha256: digestProcessCommitment(comparisonContract),
|
||||
},
|
||||
}),
|
||||
},
|
||||
confidence: "observed",
|
||||
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",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(result),
|
||||
result: result,
|
||||
});
|
||||
expect(parseEvidence(invalidResult)).toEqual(invalidResult);
|
||||
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",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...rest,
|
||||
manifest,
|
||||
exit: {
|
||||
@@ -577,7 +577,7 @@ it("keeps structural capture issues alongside semantic ones through MCP and the
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
const described =
|
||||
"(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 { 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 { parseProcessCapture } from "../../../src/domain/process/processCaptureParsing.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,
|
||||
right_evidence_id: right.evidence_id,
|
||||
},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...nativeResult,
|
||||
[dimension]: "contradiction",
|
||||
status,
|
||||
}),
|
||||
},
|
||||
confidence: "derived",
|
||||
authority: "analyst-inference",
|
||||
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 { processTraceCaptureFixture } from "../../../src/domain/process/processCapture.fixture.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { observed as ok } from "../../fixtures/analysisExecution.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -25,7 +25,7 @@ const captureEvidence = (side: "left" | "right") =>
|
||||
predicateType: "rea.process-capture",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: { side },
|
||||
result: jsonValueSchema.parse(capture),
|
||||
result: capture,
|
||||
confidence: "observed",
|
||||
authority: "controlled-replay",
|
||||
locations: [{ kind: "artifact-path", path: `/fixture/${side}` }],
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("guided prompt completion from investigation records", () => {
|
||||
const inspectionEvidence = createEvidence(undefined, fixtureProvider, {
|
||||
operation: "inspect_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventoryInspection),
|
||||
result: inventoryInspection,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
evidenceLinks: inventoryInspection.evidence_links,
|
||||
@@ -426,7 +426,7 @@ const createPagedInspectionEvidence = async (
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inventoryResult),
|
||||
result: inventoryResult,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -530,7 +530,7 @@ const createPagedInspectionEvidence = async (
|
||||
{
|
||||
operation: "inspect_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(result),
|
||||
result: result,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
evidenceLinks: result.evidence_links,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.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 { processCaptureSchema } from "../../../src/domain/process/processCapture.js";
|
||||
@@ -153,7 +153,7 @@ it("records capture disagreement as a contradicted unknown", async () => {
|
||||
predicateType: "rea.process-capture",
|
||||
operation: "capture_process_scenario",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(capture),
|
||||
result: capture,
|
||||
confidence: "observed",
|
||||
authority: "controlled-replay",
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
@@ -44,7 +44,7 @@ const pathSource = () => {
|
||||
...template.parameters,
|
||||
observation: "workflow-unknown-path",
|
||||
},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...dossier,
|
||||
callees: [
|
||||
{
|
||||
@@ -54,7 +54,7 @@ const pathSource = () => {
|
||||
body: { available: false, reason: "No body was observed." },
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
confidence: template.confidence,
|
||||
authority: template.authority,
|
||||
relationshipCoverage: { status: "complete" },
|
||||
@@ -76,10 +76,8 @@ const incompleteRight = (suffix: string) => {
|
||||
{
|
||||
operation: "analyze_function",
|
||||
parameters: { ...template.parameters, observation: suffix },
|
||||
result: jsonValueSchema.parse(
|
||||
functionDossierSchema.parse(
|
||||
FUNCTION_COMPARISON_EXAMPLE.left.normalized_result,
|
||||
),
|
||||
result: functionDossierSchema.parse(
|
||||
FUNCTION_COMPARISON_EXAMPLE.left.normalized_result,
|
||||
),
|
||||
confidence: template.confidence,
|
||||
authority: template.authority,
|
||||
|
||||
@@ -12,15 +12,17 @@ import {
|
||||
compareManagedMemberPaths,
|
||||
} from "../../../../src/application/managed/ManagedMemberComparisonService.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 { createEvidence } from "../../../../src/domain/evidence.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 { managedPeFixtureTarget } from "../../../../src/dotnet/ManagedPe.fixture.js";
|
||||
import {
|
||||
managedPeFixtureTarget,
|
||||
buildManagedNestedTypeFixture,
|
||||
buildManagedPeFixture,
|
||||
} from "../../../../src/dotnet/ManagedPe.fixture.js";
|
||||
@@ -139,7 +141,7 @@ const compare = (
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(change?.(inspection) ?? inspection),
|
||||
result: change?.(inspection) ?? inspection,
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -475,14 +477,14 @@ describe("managed member comparison subject identity", () => {
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inspection,
|
||||
artifact: { ...inspection.artifact, sha256: otherDigest },
|
||||
identity_scope: {
|
||||
...inspection.identity_scope,
|
||||
requires_artifact_sha256: otherDigest,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
const aligned = createEvidence(
|
||||
@@ -563,11 +565,11 @@ describe("managed call-target comparison", () => {
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inspection,
|
||||
call_edges:
|
||||
alterEdges?.(inspection.call_edges) ?? inspection.call_edges,
|
||||
}),
|
||||
},
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
@@ -636,10 +638,10 @@ describe("managed member comparison path workflow", () => {
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inspection,
|
||||
coverage: { ...inspection.coverage, state: "partial" },
|
||||
}),
|
||||
},
|
||||
confidence: "observed",
|
||||
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();
|
||||
});
|
||||
});
|
||||
Vendored
+2
-3
@@ -1,6 +1,5 @@
|
||||
import { artifactInventoryResultSchema } from "../../src/domain/artifactGraph.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. */
|
||||
export const fragmentInventoryEvidence = (
|
||||
@@ -26,7 +25,7 @@ export const fragmentInventoryEvidence = (
|
||||
parameters: evidence.parameters,
|
||||
confidence: "derived",
|
||||
authority: evidence.authority,
|
||||
result: jsonValueSchema.parse({
|
||||
result: {
|
||||
...inventory,
|
||||
nodes: inventory.nodes.filter(
|
||||
(_, ordinal) => ordinal % count === index,
|
||||
@@ -37,7 +36,7 @@ export const fragmentInventoryEvidence = (
|
||||
edges: inventory.edges.filter(
|
||||
(_, ordinal) => ordinal % count === index,
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
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 type { NativeSymbolMetadata } from "../../src/domain/native/nativeSymbol.js";
|
||||
import { functionDossierSchema } from "../../src/domain/hopperValues.js";
|
||||
@@ -24,7 +24,7 @@ export const nativeMatchingInventory = (
|
||||
createEvidence(nativeMatchingTarget(side), nativeMatchingProvider, {
|
||||
operation: "list_procedures",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(entries),
|
||||
result: entries,
|
||||
evidenceLinks: [...evidenceLinks],
|
||||
});
|
||||
/** Explicit imported-origin fixture for tests that select exact-symbol correspondence. */
|
||||
@@ -97,7 +97,7 @@ export const nativeMatchingDetails = (
|
||||
observation = createEvidence(target, nativeMatchingProvider, {
|
||||
operation: "analyze_function",
|
||||
parameters: { procedure: address },
|
||||
result: jsonValueSchema.parse(value),
|
||||
result: value,
|
||||
relationshipCoverage: { status: "complete" },
|
||||
});
|
||||
} else
|
||||
|
||||
+39
@@ -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
@@ -1,7 +1,7 @@
|
||||
import { readyReviewedLedgerRequest } from "../../src/application/ReconstructionObligationLedger.fixture.js";
|
||||
import { createEvidence } from "../../src/domain/evidence.js";
|
||||
import { createEvidenceBundle } from "../../src/domain/evidenceBundle.js";
|
||||
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
|
||||
|
||||
import { reconstructionObligationProofSchema } from "../../src/domain/reconstructionObligationProof.js";
|
||||
import type { ReconstructionObligationLedgerInput } from "../../src/domain/reconstructionObligationLedgerSchemas.js";
|
||||
|
||||
@@ -16,7 +16,7 @@ const replaceProof = (
|
||||
predicateType: original.predicate_type,
|
||||
operation: original.operation,
|
||||
parameters: original.parameters,
|
||||
result: jsonValueSchema.parse(result),
|
||||
result: result,
|
||||
confidence: original.confidence,
|
||||
authority: original.authority,
|
||||
});
|
||||
|
||||
Vendored
+1
-2
@@ -5,7 +5,6 @@ import { browserScenarioCaptureSchema } from "../../src/domain/browserScenarioCa
|
||||
import { DEFAULT_BROWSER_SCENARIO_CAPTURE_LIMITS } from "../../src/domain/browserScenarioCaptureLimits.js";
|
||||
import { browserScenarioEventSchema } from "../../src/domain/browserScenarioCaptureValues.js";
|
||||
import { createEvidence } from "../../src/domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
|
||||
|
||||
/** Source-owned script capture with independently authored bytes. */
|
||||
export const scriptScenarioFixture = (
|
||||
@@ -134,7 +133,7 @@ export const scriptCaptureEvidenceFixture = () =>
|
||||
{ id: "source-fixture", name: "Source fixture", version: "1" },
|
||||
{
|
||||
operation: "capture_browser_scenario",
|
||||
result: jsonValueSchema.parse(scriptScenarioFixture()),
|
||||
result: scriptScenarioFixture(),
|
||||
parameters: {},
|
||||
confidence: "observed",
|
||||
authority: "controlled-replay",
|
||||
|
||||
Reference in new issue
Block a user