You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(threat-model)!: save models with results and support offline export - #1133
Save threat models with scan results and export them without rerunning inference, including when a scan stops or its Markdown document cannot be written.
Changes
Retain canonical model content and provenance across Standard, Deep, Diff, and security-policy workflows. Expose verified model paths in SDK, history, component, and bulk results.
Add export [result-dir] --artifact threat-model with the alternative source selector --scan IDPREFIX. Threat-model export defaults to Markdown in ./threatmodel.md; --output - writes to stdout. Findings export retains its SARIF default. Add SDK exportArtifact and nullable threatModelPath fields.
Preserve artifact seals, source identity, legacy structured models, cancellation, and completed scan snapshots. Treat Markdown generation as optional while retaining canonical content and warnings.
Recover the selected worker checkpoint model, including a newer terminal checkpoint saved before result.json is replaced. Preserve a newer completed result and freeze the selected model with source digests so publication retries cannot change it.
Merge current main, update the embedded workflow hash and snapshot metadata source, and remove the unused relaxed saved-scan resolver.
Testing
All five portable plugin checks passed: Ruff lint and formatting, SDK build:ci, source compatibility, and its 9 regression tests.
SDK and MCP types, SDK formatting, and bundled plugin build passed with the pinned dependencies.
The terminal-model ordering regression passed all 5 cases, including explicit and default completion. The expanded saved-model publication retry checks passed all 15 cases.
All 30 focused MCP checks passed across storage, projection, state fallback, and Deep stdio lifecycle (the lifecycle check was rerun with a standard umask).
The local npm archive validated all 536 entries. Installed-package smoke could not complete because the registry returned ETARGET for the pinned @linear/sdk@96.0.0 dependency.
Saved-model and report-projection suites passed: 73 tests and 53 subtests.
Focused SDK coverage passed 408 tests (1 platform skip) across 14 files with seed 12345. Three Python workbench suites passed all 166 tests.
Three independent native code reviews and a fresh verification pass found no remaining actionable issues.
Full SDK suites completed: seed 12345 had 3,471 passed and 5 failed; normal order had 3,473 passed and 3 failed (53 skipped in each). Four workflow cases hit the 30s timeout and one TUI case resolved the wrong local dependencies. All five distinct failing cases passed focused reruns with the pinned dependencies and a 120s workflow-test timeout.
Risk and rollout
This adds public export syntax and SDK fields. SecurityPolicyDraft.threatModelPath can be null when its convenience document cannot be written; canonical model content remains exportable. Historical structured models remain readable. Saved-artifact export does not run model inference. No migration or deployment step is required.
Public disclosure review
No customer, partner, prospect, or user identities, data, or identifying details are included.
No credentials, personal data, private source, scan findings, or nonpublic links or tickets are included.
I reviewed the branch name, title, description, commits, changes, comments, logs, screenshots, attachments, and links for public disclosure.
The reason will be displayed to describe this comment to others. Learn more.
Withholding approval: package validation currently fails, and the existing cancellation, stale-projection and blank-Markdown issues remain applicable after tracing their callers (discussion_r4150025654, discussion_r4150025660, discussion_r4150025667). Reviewed changed hunks and integration paths via source and hosted CI; no local execution. The package/container failure below is confirmed in hosted logs, not inferred from the PR description.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
Cancellation can be swallowed, completed policies can expose stale provisional documents, and legacy model paths can escape through symlinked parent directories.
Addressed the projection-warning item in this review in 3622cf8. Final projection warnings now use the existing completion and recovery warning lists, including repeated completion and sealed-result refresh. Saved history and SDK responses retain the warning once. The regression tests verify successful completion and unchanged canonical content when the optional Markdown document cannot be written.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
Failed post-scan recovery can leave a newly created model projection that supersedes the completed snapshot.
Review effort: Balanced Findings: None
Previously missed (1)
In code that hasn't changed since last review
Remove newly created threatmodel.md after failed post-scan
sdk/typescript/src/api.ts:2184
A failed post-scan turn can leave a newly created threatmodel.md outside the restored snapshot. This list only records a model document when one existed before the turn. If a completed legacy/no-model scan starts with threatModelPath === null, the follow-up can write a canonical model/projection and then fail; the manifest is restored, but the new root document remains. The legacy fallback then treats that file as the scan's saved model, so later history/export no longer reflects the completed snapshot. Track the absent state for threatmodel.md and remove a file created by a failed follow-up (while retaining the existing safe-path restoration checks).
Reviewed the “Previously missed 1” rollback concern in this review.
Follow-up instructions start after complete-scan seals the result. A successful rollback restores that manifest and its recorded artifacts. The saved-model reader returns no model for a sealed manifest without a canonical model or recorded legacy path, before inspecting a newly created threatmodel.md. Recorded legacy documents are already included in the artifact snapshot, even when threatModelPath is null. Canonical models remain authoritative; a mismatching projection returns a null path.
The existing test_sealed_scan_does_not_expose_unrecorded_legacy_models covers all four unrecorded legacy filenames and verifies that lookup and export remain unavailable. See the sealed reader and regression. The proposed model adoption does not occur under this contract, so no deletion behavior was added.
Updated through 70ac5e62 to include current main, preserving saved-model export and recovery behavior. Terminal Deep drafts now retain an omitted canonical model while replacing older findings and coverage.
All five portable plugin checks, SDK/MCP types, SDK formatting, and the bundled plugin build passed. The MCP suites passed 189 tests, and the affected Python publication/recovery suites passed 342 tests. Both complete SDK runs passed 3,565 tests with 53 skips and zero failures (seeds 12345 and 3811945789). Three independent native reviews plus fresh verification found no actionable issues.
All required checks passed for 70ac5e62 (CI run). All 38 review threads are resolved.
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 70ac5e628df8d73dde495b958f7a4084baee9cfa against 3642a76a53173e012f579737d1b6cf4096379af2 with exactly three independent full-diff passes, including all changed tests, and independent root verification. No serious introduced findings remain. The inline P3 is a test portability issue; all four legacy export controls preserve the original bytes and resolve to the same physical file.
Validation on macOS arm64: both full SDK runs (seeds 12345 and 2571051859) had 3,553 passes, 64 skips and the same single sandbox-denied ps check; that unchanged test passed with permission. Touched Python tests had 524 passes and 52 passing subtests, with only the inline filename-spelling assertion failing. All 36 distinct touched MCP outcomes passed after one watcher-permission rerun. Fresh native build, plugin/SDK builds, types, formatting, Ruff 0.16.9 and portable checks passed.
The 540-entry package archive check passed; full installed-package smoke could not reach its assertions because npm returned ETARGET for @types/node@26.6.3. No model execution or local Windows run. All reviewed source files still match the head.
The reason will be displayed to describe this comment to others. Learn more.
[P3] Compare file identity for legacy names on case-insensitive filesystems
The threat_model.md subtest fails on a case-insensitive macOS volume: discovery checks THREAT_MODEL.md first and returns that spelling for the same physical file, so this exact string comparison fails even though export succeeds. I reproduced the assertion failure and separately verified all four legacy variants export identical bytes without changing the source. Compare Path(returned_path).samefile(path) here, while keeping the byte-preservation assertions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
breaking-changeBreaking change called out in generated release notes
5 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Save threat models with scan results and export them without rerunning inference, including when a scan stops or its Markdown document cannot be written.
Changes
export [result-dir] --artifact threat-modelwith the alternative source selector--scan IDPREFIX. Threat-model export defaults to Markdown in./threatmodel.md;--output -writes to stdout. Findings export retains its SARIF default. Add SDKexportArtifactand nullablethreatModelPathfields.result.jsonis replaced. Preserve a newer completed result and freeze the selected model with source digests so publication retries cannot change it.Testing
build:ci, source compatibility, and its 9 regression tests.ETARGETfor the pinned@linear/sdk@96.0.0dependency.Risk and rollout
This adds public export syntax and SDK fields.
SecurityPolicyDraft.threatModelPathcan benullwhen its convenience document cannot be written; canonical model content remains exportable. Historical structured models remain readable. Saved-artifact export does not run model inference. No migration or deployment step is required.Public disclosure review