stacktrace-cli 0.0.1__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stacktrace_cli-0.1.0/.agents/skills/release-stacktrace/SKILL.md +233 -0
- stacktrace_cli-0.1.0/.codex/hooks.json +34 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/autofix.yml +1 -1
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/claude.yml +10 -4
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/CLAUDE.md +45 -0
- stacktrace_cli-0.1.0/PKG-INFO +228 -0
- stacktrace_cli-0.1.0/README.md +207 -0
- stacktrace_cli-0.1.0/docs/adrs/0001-session-telemetry-as-input.md +116 -0
- stacktrace_cli-0.1.0/docs/adrs/0002-session-collection-in-openaidr.md +159 -0
- stacktrace_cli-0.1.0/docs/adrs/0003-runtime-edges.md +97 -0
- stacktrace_cli-0.1.0/docs/adrs/0004-three-stage-detector.md +157 -0
- stacktrace_cli-0.1.0/docs/adrs/0005-detection-family-and-report-assembly.md +167 -0
- stacktrace_cli-0.1.0/docs/adrs/0006-trust-boundary-and-detection-upload.md +179 -0
- stacktrace_cli-0.1.0/docs/adrs/0007-proprietary-package-on-open-dependencies.md +108 -0
- stacktrace_cli-0.1.0/docs/adrs/0008-console-in-separate-demo-package.md +77 -0
- stacktrace_cli-0.1.0/docs/adrs/0010-detection-severity-and-confidence-ladders.md +144 -0
- stacktrace_cli-0.1.0/docs/adrs/0011-verdict-cache.md +110 -0
- stacktrace_cli-0.1.0/docs/adrs/0012-observation-evidence-kinds-and-transport.md +116 -0
- stacktrace_cli-0.1.0/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +194 -0
- stacktrace_cli-0.1.0/docs/adrs/0014-declared-project-mapping.md +121 -0
- stacktrace_cli-0.1.0/docs/adrs/0015-narrow-the-security-catalogue.md +120 -0
- stacktrace_cli-0.1.0/docs/adrs/0016-stall-grouping-is-session-wide.md +94 -0
- stacktrace_cli-0.1.0/docs/adrs/0017-a-declined-repeat-is-a-stall.md +100 -0
- stacktrace_cli-0.1.0/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +97 -0
- stacktrace_cli-0.1.0/docs/adrs/0019-delegated-commands-are-openaca-objects.md +85 -0
- stacktrace_cli-0.1.0/docs/adrs/0020-openaca-consumption-boundary.md +126 -0
- stacktrace_cli-0.1.0/docs/adrs/0021-two-command-kinds.md +99 -0
- stacktrace_cli-0.1.0/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +90 -0
- stacktrace_cli-0.1.0/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +84 -0
- stacktrace_cli-0.1.0/docs/adrs/0024-the-upload-carries-observations.md +86 -0
- stacktrace_cli-0.1.0/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +92 -0
- stacktrace_cli-0.1.0/docs/adrs/INDEX.md +60 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/docs/adrs/TEMPLATE.md +2 -0
- stacktrace_cli-0.1.0/docs/plans/002-session-input.md +403 -0
- stacktrace_cli-0.1.0/docs/plans/003-correlation.md +890 -0
- stacktrace_cli-0.1.0/docs/plans/004-detector.md +522 -0
- stacktrace_cli-0.1.0/docs/plans/005-remote-sync.md +1346 -0
- stacktrace_cli-0.1.0/docs/plans/006-cli-composition.md +899 -0
- stacktrace_cli-0.1.0/docs/plans/007-detection-upload.md +313 -0
- stacktrace_cli-0.1.0/docs/releases/v0.1.0.md +74 -0
- stacktrace_cli-0.1.0/docs/specs/aidr.md +477 -0
- stacktrace_cli-0.1.0/docs/specs/cli-composition.md +375 -0
- stacktrace_cli-0.1.0/docs/specs/correlation.md +585 -0
- stacktrace_cli-0.1.0/docs/specs/detection-upload.md +441 -0
- stacktrace_cli-0.1.0/docs/specs/detector.md +1076 -0
- stacktrace_cli-0.1.0/docs/specs/remote-sync.md +606 -0
- stacktrace_cli-0.1.0/docs/specs/session-input.md +175 -0
- stacktrace_cli-0.1.0/pyproject.toml +94 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/src/stacktrace_cli/__init__.py +1 -1
- stacktrace_cli-0.1.0/src/stacktrace_cli/__main__.py +16 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/cli.py +314 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/__init__.py +1 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/acquire.py +373 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/composition.py +375 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/join.py +380 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/observed.py +1836 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/orchestrate.py +417 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/project_map.py +81 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/record.py +279 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/render.py +766 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/__init__.py +1 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/analyzer.py +207 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/cache.py +483 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/deterministic.py +606 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/finding.py +247 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/markers.py +158 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/priors.py +502 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/__init__.py +36 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/exclusions.md +36 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/framing.md +23 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +12 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/stacktrace-injected-instruction-followed.md +14 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/stacktrace-intent-drift.md +11 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/reasoning.py +1180 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/render.py +310 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/rules.py +136 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/run.py +453 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/secrets.py +203 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/detector/verdict.py +266 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/__init__.py +1 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/cli.py +475 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/client.py +393 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/config.py +106 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/detect_payload.py +262 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/payload.py +435 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/policy.py +88 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/redact.py +649 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/spool.py +446 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/sync.py +558 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/sync_detect.py +409 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/remote/upload_contract.py +973 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/__init__.py +1 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/access.py +79 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/outcome.py +65 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/protocols.py +204 -0
- stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/render.py +295 -0
- stacktrace_cli-0.1.0/tests/__init__.py +0 -0
- stacktrace_cli-0.1.0/tests/agent_bom_fixture.py +252 -0
- stacktrace_cli-0.1.0/tests/detector_session_fixture.py +137 -0
- stacktrace_cli-0.1.0/tests/remote/__init__.py +0 -0
- stacktrace_cli-0.1.0/tests/remote/helpers.py +126 -0
- stacktrace_cli-0.1.0/tests/remote/test_cli.py +826 -0
- stacktrace_cli-0.1.0/tests/remote/test_client.py +545 -0
- stacktrace_cli-0.1.0/tests/remote/test_config.py +118 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_activity.py +168 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_cli.py +72 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_client.py +59 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_contract_is_exhaustive.py +832 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_gate_matches_the_cloud.py +351 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_layers_compose.py +199 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_redaction.py +142 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_spool.py +329 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_upload_contract.py +180 -0
- stacktrace_cli-0.1.0/tests/remote/test_detect_wire_payload.py +185 -0
- stacktrace_cli-0.1.0/tests/remote/test_facade_contract.py +213 -0
- stacktrace_cli-0.1.0/tests/remote/test_payload.py +937 -0
- stacktrace_cli-0.1.0/tests/remote/test_policy.py +259 -0
- stacktrace_cli-0.1.0/tests/remote/test_redact_payload.py +985 -0
- stacktrace_cli-0.1.0/tests/remote/test_seam.py +260 -0
- stacktrace_cli-0.1.0/tests/remote/test_spool.py +693 -0
- stacktrace_cli-0.1.0/tests/remote/test_sync.py +1718 -0
- stacktrace_cli-0.1.0/tests/remote/test_sync_detect.py +576 -0
- stacktrace_cli-0.1.0/tests/remote/test_upload_contract.py +853 -0
- stacktrace_cli-0.1.0/tests/test_acquire.py +457 -0
- stacktrace_cli-0.1.0/tests/test_bom_shape.py +196 -0
- stacktrace_cli-0.1.0/tests/test_call_status_conventions.py +101 -0
- stacktrace_cli-0.1.0/tests/test_cli.py +330 -0
- stacktrace_cli-0.1.0/tests/test_composition.py +520 -0
- stacktrace_cli-0.1.0/tests/test_correlate_orchestrate.py +457 -0
- stacktrace_cli-0.1.0/tests/test_correlate_render.py +760 -0
- stacktrace_cli-0.1.0/tests/test_correlated_session.py +278 -0
- stacktrace_cli-0.1.0/tests/test_detector_analyzer_contract.py +243 -0
- stacktrace_cli-0.1.0/tests/test_detector_analyzer_live.py +158 -0
- stacktrace_cli-0.1.0/tests/test_detector_cache.py +633 -0
- stacktrace_cli-0.1.0/tests/test_detector_deterministic.py +367 -0
- stacktrace_cli-0.1.0/tests/test_detector_end_to_end.py +278 -0
- stacktrace_cli-0.1.0/tests/test_detector_finding.py +199 -0
- stacktrace_cli-0.1.0/tests/test_detector_liveness.py +698 -0
- stacktrace_cli-0.1.0/tests/test_detector_markers.py +150 -0
- stacktrace_cli-0.1.0/tests/test_detector_priors.py +710 -0
- stacktrace_cli-0.1.0/tests/test_detector_reasoning.py +1517 -0
- stacktrace_cli-0.1.0/tests/test_detector_render.py +325 -0
- stacktrace_cli-0.1.0/tests/test_detector_run.py +809 -0
- stacktrace_cli-0.1.0/tests/test_detector_secrets.py +292 -0
- stacktrace_cli-0.1.0/tests/test_detector_verdict.py +465 -0
- stacktrace_cli-0.1.0/tests/test_fingerprint_covers_what_the_analyzer_reads.py +383 -0
- stacktrace_cli-0.1.0/tests/test_help_sections.py +147 -0
- stacktrace_cli-0.1.0/tests/test_join.py +255 -0
- stacktrace_cli-0.1.0/tests/test_observed.py +747 -0
- stacktrace_cli-0.1.0/tests/test_openaca_contract.py +66 -0
- stacktrace_cli-0.1.0/tests/test_openaidr_contract.py +173 -0
- stacktrace_cli-0.1.0/tests/test_option_arity_matches_the_binary.py +519 -0
- stacktrace_cli-0.1.0/tests/test_project_map.py +270 -0
- stacktrace_cli-0.1.0/tests/test_readme_examples_are_real_output.py +69 -0
- stacktrace_cli-0.1.0/tests/test_release_readiness.py +59 -0
- stacktrace_cli-0.1.0/tests/test_seam_boundary.py +104 -0
- stacktrace_cli-0.1.0/tests/test_sessions_access.py +251 -0
- stacktrace_cli-0.1.0/tests/test_sessions_end_to_end.py +92 -0
- stacktrace_cli-0.1.0/tests/test_sessions_protocol_typing.py +18 -0
- stacktrace_cli-0.1.0/tests/test_sessions_protocols.py +59 -0
- stacktrace_cli-0.1.0/tests/test_sessions_render.py +503 -0
- stacktrace_cli-0.1.0/tests/test_shell_separators_match_the_shell.py +1288 -0
- stacktrace_cli-0.1.0/tests/test_untrusted_content_never_travels.py +278 -0
- stacktrace_cli-0.1.0/tests/test_verification_subcommands_match_the_binary.py +185 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/uv.lock +46 -6
- stacktrace_cli-0.0.1/PKG-INFO +0 -39
- stacktrace_cli-0.0.1/README.md +0 -22
- stacktrace_cli-0.0.1/docs/adrs/INDEX.md +0 -28
- stacktrace_cli-0.0.1/pyproject.toml +0 -50
- stacktrace_cli-0.0.1/src/stacktrace_cli/__main__.py +0 -24
- stacktrace_cli-0.0.1/tests/test_cli.py +0 -20
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.claude/settings.json +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.claude/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/ci.yml +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/publish-pypi.yml +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.gitignore +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/AGENTS.md +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/docs/adrs/HOOK-PROMPT.md +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/docs/releases/v0.0.1.md +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/scripts/git-hooks/pre-push +0 -0
- {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/scripts/install-hooks.sh +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-stacktrace
|
|
3
|
+
description: Use when cutting a new stacktrace-cli PyPI release. Walks through version bump, release-notes drafting, pre-flight checks, tag, and push — enforces the publish-pypi.yml notes-file gate so the GitHub Releases page can't silently drift from PyPI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# release-stacktrace
|
|
7
|
+
|
|
8
|
+
End-to-end release procedure for the `stacktrace-cli` distribution.
|
|
9
|
+
`.github/workflows/publish-pypi.yml` enforces the machine-checkable gates
|
|
10
|
+
(tag on main, tag matches both version strings, notes file present, four
|
|
11
|
+
CI gates green). This skill is the human-side counterpart: it drafts the
|
|
12
|
+
notes with judgment, runs the pre-flight checks, and pushes the tag.
|
|
13
|
+
|
|
14
|
+
**Announce at start:** "I'm using the release-stacktrace skill to cut a new
|
|
15
|
+
stacktrace-cli release."
|
|
16
|
+
|
|
17
|
+
## When to invoke
|
|
18
|
+
|
|
19
|
+
User says any of: "cut a release", "ship 0.0.N", "release stacktrace",
|
|
20
|
+
"tag a new version", or starts editing `pyproject.toml`'s version field
|
|
21
|
+
in this repo.
|
|
22
|
+
|
|
23
|
+
## Inputs
|
|
24
|
+
|
|
25
|
+
1. **Target version** (e.g. `0.1.0`). Inferred from `pyproject.toml` if
|
|
26
|
+
the user already bumped it; otherwise ask.
|
|
27
|
+
2. **Theme** — the H1 line of the notes file. Ask the user; a commit log
|
|
28
|
+
can't infer it.
|
|
29
|
+
|
|
30
|
+
## Procedure
|
|
31
|
+
|
|
32
|
+
### Step 1. Pre-flight checks
|
|
33
|
+
|
|
34
|
+
Run from the repo root. Stop and ask if any fails — do not "fix" a dirty
|
|
35
|
+
tree or out-of-sync main automatically, those signal in-flight work.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git rev-parse --abbrev-ref HEAD # must be main
|
|
39
|
+
git status --porcelain # must be empty
|
|
40
|
+
git fetch --tags origin
|
|
41
|
+
git rev-parse --verify "v<version>" 2>/dev/null # MUST FAIL — tag must not exist
|
|
42
|
+
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`tests/test_release_readiness.py -m release_gate` is **not** run here: it
|
|
46
|
+
requires `openaca`/`openaidr` pinned exactly, which is not true of a normal
|
|
47
|
+
checkout (both float on `>=` between releases) and is only made true by
|
|
48
|
+
Step 4. Running it against an unpinned tree would fail on every release, not
|
|
49
|
+
just a broken one. It runs at the end of Step 4, once its precondition holds.
|
|
50
|
+
|
|
51
|
+
PyPI versions are immutable, including yanked ones. If `<version>` is
|
|
52
|
+
already on PyPI, the only path forward is a new version number:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -s https://pypi.org/pypi/stacktrace-cli/json | python3 -c 'import json,sys; print(sorted(json.load(sys.stdin)["releases"]))'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 2. Gather the commit log
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
prev_tag=$(git describe --tags --abbrev=0) # empty on the first release
|
|
62
|
+
git log --oneline "${prev_tag}..HEAD"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Raw material only — do not dump it into the notes file verbatim.
|
|
66
|
+
|
|
67
|
+
### Step 3. Draft `docs/releases/v<version>.md`
|
|
68
|
+
|
|
69
|
+
Follow the shape of the existing files in `docs/releases/`:
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
# <version> — <theme>
|
|
73
|
+
|
|
74
|
+
## Highlights
|
|
75
|
+
|
|
76
|
+
- **<Theme A>.** 1-3 sentences pitched at someone who installs the CLI.
|
|
77
|
+
Reference ADRs by number when a design doc backs the change.
|
|
78
|
+
- **Bug fixes.** Combine small fixes into one bullet, comma-separated.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
`uv tool install stacktrace-cli==<version>` or `pip install stacktrace-cli==<version>`.
|
|
83
|
+
|
|
84
|
+
## Compatibility
|
|
85
|
+
|
|
86
|
+
<Behavior changes existing users will notice. Say "pre-alpha, no
|
|
87
|
+
back-compat hedging" while that's still true.>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Judgment guidelines (apply ruthlessly):**
|
|
91
|
+
|
|
92
|
+
- **Group by theme, not by commit.** Five commits adding one capability
|
|
93
|
+
are one bullet.
|
|
94
|
+
- **Lead with user-visible impact**, not implementation.
|
|
95
|
+
- **Cut chore/format/docs-only and release-machinery commits.** They
|
|
96
|
+
aren't release-visible.
|
|
97
|
+
- **Acknowledge breaking changes explicitly** under `## Compatibility` —
|
|
98
|
+
a renamed subcommand or changed output shape goes there.
|
|
99
|
+
|
|
100
|
+
Show the draft to the user and iterate until they agree it captures what
|
|
101
|
+
shipped.
|
|
102
|
+
|
|
103
|
+
### Step 4. Bump the version, pin workspace dependencies, and gate
|
|
104
|
+
|
|
105
|
+
`pyproject.toml`'s `version` and `src/stacktrace_cli/__init__.py`'s
|
|
106
|
+
`__version__` are separate sources; the workflow fails the build if they
|
|
107
|
+
disagree with the tag. Move them together:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
sed -i.bak 's/^version = ".*"/version = "<version>"/' pyproject.toml && rm pyproject.toml.bak
|
|
111
|
+
sed -i.bak 's/^__version__ = ".*"/__version__ = "<version>"/' src/stacktrace_cli/__init__.py && rm src/stacktrace_cli/__init__.py.bak
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`openaca` and `openaidr` are developed alongside this package and float on
|
|
115
|
+
`>=` during normal development, but a prior release will have left them
|
|
116
|
+
pinned exactly with `==` (see below), which would stop `uv lock` from ever
|
|
117
|
+
resolving a newer release. Loosen both back to a floor first — the
|
|
118
|
+
replacement matches either operator, so this is safe to run whether the tree
|
|
119
|
+
is currently floating or still pinned from last time:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
sed -i.bak -E 's/^ "openaca(>=|==)[^"]*"/ "openaca>=0"/' pyproject.toml && rm pyproject.toml.bak
|
|
123
|
+
sed -i.bak -E 's/^ "openaidr(>=|==)[^"]*"/ "openaidr>=0"/' pyproject.toml && rm pyproject.toml.bak
|
|
124
|
+
uv lock # refresh uv.lock, now free to pick the newest release
|
|
125
|
+
uv run stacktrace --version # sanity: prints <version>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Now pin both to whatever `uv lock` just resolved — the one point in the
|
|
129
|
+
procedure where the lockfile has just picked the newest available versions
|
|
130
|
+
and a release is actually being cut:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
openaca_version=$(grep -A1 '^name = "openaca"' uv.lock | grep version | cut -d'"' -f2)
|
|
134
|
+
openaidr_version=$(grep -A1 '^name = "openaidr"' uv.lock | grep version | cut -d'"' -f2)
|
|
135
|
+
sed -i.bak -E "s/^ \"openaca(>=|==)[^\"]*\"/ \"openaca==${openaca_version}\"/" pyproject.toml && rm pyproject.toml.bak
|
|
136
|
+
sed -i.bak -E "s/^ \"openaidr(>=|==)[^\"]*\"/ \"openaidr==${openaidr_version}\"/" pyproject.toml && rm pyproject.toml.bak
|
|
137
|
+
uv lock # refresh uv.lock for the exact pin
|
|
138
|
+
uv run pytest tests/test_release_readiness.py -m release_gate
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The gate is not covered by `.github/workflows/publish-pypi.yml`, which runs
|
|
142
|
+
the default `uv run pytest -q` — and `pyproject.toml`'s `addopts` excludes
|
|
143
|
+
`release_gate` from that default run on purpose (it fails throughout normal
|
|
144
|
+
development, before this step pins the floors). This skill is the only place
|
|
145
|
+
it runs before a tag is pushed, so skipping it here is the one way a version
|
|
146
|
+
with an unpinned `openaidr`/`openaca` requirement reaches PyPI. Stop and ask
|
|
147
|
+
if it fails.
|
|
148
|
+
|
|
149
|
+
### Step 5. Release-prep commit + PR
|
|
150
|
+
|
|
151
|
+
If the user's original request did not explicitly ask to cut, ship, or
|
|
152
|
+
publish a release, stop before the push and ask. Editing the version
|
|
153
|
+
field is enough to invoke this skill; it is not permission to publish.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git checkout -b release/<version>
|
|
157
|
+
git add pyproject.toml uv.lock src/stacktrace_cli/__init__.py docs/releases/v<version>.md
|
|
158
|
+
git status --porcelain # inspect anything still unstaged; stage intentionally or stop
|
|
159
|
+
git commit -m "release: <version> — <theme>"
|
|
160
|
+
git push -u origin release/<version>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Opening the PR needs its own explicit ask. Then:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
gh pr create --title "release: <version>" --body "Release prep for <version>.
|
|
167
|
+
|
|
168
|
+
- Bumps \`pyproject.toml\` and \`__version__\` to <version>
|
|
169
|
+
- Adds release notes at \`docs/releases/v<version>.md\`
|
|
170
|
+
- After merge: tag \`v<version>\` on main and push to trigger PyPI publish + GitHub Release"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Surface the PR URL. **Stop here.** The user reviews and merges.
|
|
174
|
+
|
|
175
|
+
### Step 6. After merge: tag + push
|
|
176
|
+
|
|
177
|
+
After the user confirms the PR merged, on a clean main:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
git checkout main
|
|
181
|
+
git pull --ff-only
|
|
182
|
+
git tag v<version>
|
|
183
|
+
git push origin v<version>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The tag-triggered run registers asynchronously, so `--limit 1` can grab a
|
|
187
|
+
stale earlier run. Poll for the run tied to the tagged commit:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
tag_sha=$(git rev-parse "v<version>^{commit}")
|
|
191
|
+
run_id=""
|
|
192
|
+
until [ -n "$run_id" ]; do
|
|
193
|
+
run_id=$(gh run list --repo stacktrace-ai/stacktrace \
|
|
194
|
+
--workflow publish-pypi.yml --commit "$tag_sha" \
|
|
195
|
+
--json databaseId --jq '.[0].databaseId // empty')
|
|
196
|
+
[ -z "$run_id" ] && sleep 5
|
|
197
|
+
done
|
|
198
|
+
gh run watch --repo stacktrace-ai/stacktrace "$run_id"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Step 7. Verify
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
curl -s https://pypi.org/pypi/stacktrace-cli/json | python3 -c 'import json,sys; print(sorted(json.load(sys.stdin)["releases"]))'
|
|
205
|
+
gh release view v<version> --repo stacktrace-ai/stacktrace --json name,body
|
|
206
|
+
uvx --from stacktrace-cli==<version> stacktrace --version # installs from PyPI, must print <version>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
All three must succeed. If any fails, surface the workflow run URL rather
|
|
210
|
+
than guessing.
|
|
211
|
+
|
|
212
|
+
## Failure modes this prevents
|
|
213
|
+
|
|
214
|
+
1. **On PyPI but not on GitHub Releases.** The workflow's notes-file gate
|
|
215
|
+
runs before publish: no notes file at the tag → no PyPI upload.
|
|
216
|
+
2. **Notes file is a raw commit dump.** Step 3's judgment guidelines.
|
|
217
|
+
3. **Tagging a non-main commit.** The workflow's "Verify tag is on main".
|
|
218
|
+
4. **Tag ≠ `pyproject.toml` version, or `__version__` drift.** Two
|
|
219
|
+
separate workflow steps; Step 4 keeps them moving together.
|
|
220
|
+
5. **Forgetting to refresh `uv.lock`.** `uv sync --frozen` fails in the
|
|
221
|
+
build job; Step 4 bumps the lock to avoid the round-trip.
|
|
222
|
+
|
|
223
|
+
## What this skill does NOT do
|
|
224
|
+
|
|
225
|
+
- **Doesn't publish to PyPI directly.** The workflow does, via Trusted
|
|
226
|
+
Publishing (OIDC). There is no PyPI token anywhere in this repo, and
|
|
227
|
+
none should be added.
|
|
228
|
+
- **Doesn't create the GitHub Release directly.** The workflow's
|
|
229
|
+
`release-github` job does, from the notes file.
|
|
230
|
+
- **Doesn't write a CHANGELOG.md.** The convention is per-release files
|
|
231
|
+
under `docs/releases/`.
|
|
232
|
+
- **Doesn't yank.** PyPI versions are immutable; a broken release is
|
|
233
|
+
fixed by shipping the next version, not by re-uploading.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreCompact": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE COMPACTION ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"SessionStart": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "if [ -f docs/adrs/INDEX.md ]; then printf '=== ARCHITECTURE DECISIONS (docs/adrs/) ===\\n\\nRead a full ADR before changing logic in the area it covers.\\n\\n'; cat docs/adrs/INDEX.md; fi"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"SessionEnd": [
|
|
24
|
+
{
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE SESSION END ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -111,7 +111,7 @@ jobs:
|
|
|
111
111
|
# two string representations depending on which API you read.
|
|
112
112
|
- name: Cap auto-address iterations (max 7)
|
|
113
113
|
run: |
|
|
114
|
-
MAX=
|
|
114
|
+
MAX=15
|
|
115
115
|
CLAUDE_RUNS=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
|
|
116
116
|
--jq '[.comments[] | select(.author.login == "claude")] | length')
|
|
117
117
|
if [ "$CLAUDE_RUNS" -ge "$MAX" ]; then
|
|
@@ -100,10 +100,16 @@ jobs:
|
|
|
100
100
|
uses: anthropics/claude-code-action@v1
|
|
101
101
|
with:
|
|
102
102
|
# Subscription-tied OAuth path. Provision via `claude setup-token`
|
|
103
|
-
# CLI and store as the CLAUDE_CODE_OAUTH_TOKEN repo secret.
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
103
|
+
# CLI and store as the CLAUDE_CODE_OAUTH_TOKEN repo secret.
|
|
104
|
+
#
|
|
105
|
+
# This ran on the pay-per-use API key for one day (d2c62b0) to escape
|
|
106
|
+
# the throttling the subscription's shared session window imposes on
|
|
107
|
+
# long review runs, and the spend was not worth it: this workflow is
|
|
108
|
+
# fired by every review comment on an open PR, so an active
|
|
109
|
+
# review loop bills per round with no ceiling of its own. Throttling
|
|
110
|
+
# is the cheaper failure. To switch back, replace this line with
|
|
111
|
+
# `anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}` (the secret is
|
|
112
|
+
# still provisioned) and set a workspace spend limit first.
|
|
107
113
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
108
114
|
|
|
109
115
|
# Lets Claude read CI results on PRs.
|
|
@@ -8,11 +8,21 @@ unrelated project). It is consumed by developers who `pip install stacktrace-cli
|
|
|
8
8
|
and by CI pipelines that invoke `stacktrace`, and it builds on the open-source
|
|
9
9
|
`openaca` Agent Composition Analysis toolkit.
|
|
10
10
|
|
|
11
|
+
It is also the **design home for Stacktrace Detect** — the AIDR product this CLI
|
|
12
|
+
grows into. The specs in `docs/specs/` and the decisions in `docs/adrs/` describe
|
|
13
|
+
the whole system: session input, correlation against the composition graph, the
|
|
14
|
+
three-stage detector, and the combined report. The code here is the placeholder that
|
|
15
|
+
precedes them.
|
|
16
|
+
|
|
11
17
|
## Common commands
|
|
12
18
|
|
|
13
19
|
```bash
|
|
14
20
|
uv sync # install / update deps
|
|
15
21
|
uv run stacktrace --version # run the CLI from the working tree
|
|
22
|
+
uv run stacktrace sessions # what the agents on this machine did
|
|
23
|
+
uv run stacktrace sessions --format json # the same, machine-readable
|
|
24
|
+
uv run stacktrace detect # what ran, correlated and judged
|
|
25
|
+
uv run stacktrace detect --bom b.json # against a supplied Agent BOM
|
|
16
26
|
uv build # build sdist + wheel into dist/
|
|
17
27
|
uvx --from 'twine>=7' twine check dist/* # validate artifacts (twine <7 rejects Metadata-Version 2.5)
|
|
18
28
|
```
|
|
@@ -48,6 +58,24 @@ Composition Analysis primitives this CLI is intended to drive.
|
|
|
48
58
|
The package is currently pre-alpha: `main()` reports its version and points at
|
|
49
59
|
the docs. Real functionality lands in later releases.
|
|
50
60
|
|
|
61
|
+
**The design that functionality implements is in this repo.** Read
|
|
62
|
+
`docs/specs/aidr.md` first — it is the umbrella: components, contracts, the two
|
|
63
|
+
modes, and what comes from where. Then the component specs:
|
|
64
|
+
|
|
65
|
+
| Spec | Covers |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `docs/specs/aidr.md` | Umbrella — components, contracts, tenets, delivery |
|
|
68
|
+
| `docs/specs/session-input.md` | The seam over OpenAIDR, which collects sessions |
|
|
69
|
+
| `docs/specs/correlation.md` | The join: sessions against the composition graph |
|
|
70
|
+
| `docs/specs/detector.md` | Three-stage detection and the finding family |
|
|
71
|
+
|
|
72
|
+
Two dependencies, both Apache-2.0 and neither depending on this package:
|
|
73
|
+
`openaca` for composition, and `openaidr`
|
|
74
|
+
(https://github.com/open-agent-security/openaidr) for session collection, which
|
|
75
|
+
wraps Uber's third-party `adr-sensor`. Nothing proprietary flows into either
|
|
76
|
+
(ADR-0007); the one thing we need from OpenACA is graph caching, and it names
|
|
77
|
+
nothing about detection.
|
|
78
|
+
|
|
51
79
|
## Repo conventions
|
|
52
80
|
|
|
53
81
|
- **Four names, deliberately not identical.** PyPI distribution
|
|
@@ -75,6 +103,23 @@ the docs. Real functionality lands in later releases.
|
|
|
75
103
|
API token exists in this repo, and none should be added), then creates the
|
|
76
104
|
GitHub Release from the notes file. The `release-stacktrace` skill drives
|
|
77
105
|
the human side.
|
|
106
|
+
- **OpenACA carries nothing AIDR** (ADR-0006). No finding family, no output row
|
|
107
|
+
type, no scan flag, no rule namespace upstream. Detection rule identifiers are
|
|
108
|
+
`stacktrace-*`, never `openaca-*`, and the combined report is assembled here.
|
|
109
|
+
- **Session collection is not ours** (ADR-0002). It lives in `openaidr`, open
|
|
110
|
+
source. Its types stop at our adapter; parsing an agent format here would
|
|
111
|
+
duplicate the thing the split exists to prevent.
|
|
112
|
+
- **A class-shaped review finding is closed with an invariant, not a patch.**
|
|
113
|
+
Four classes of finding recurred across this branch's review rounds — a denied
|
|
114
|
+
call counted as evidence, untrusted text echoed into a finding, an
|
|
115
|
+
unvalidated field in an external document, a prompt field missing from the
|
|
116
|
+
cache key — because each was fixed at the site a reviewer happened to read
|
|
117
|
+
while the *rule* stayed in the reviewer's head. Each is now enforced by a test
|
|
118
|
+
that asks about every site or every field rather than the one that bit us:
|
|
119
|
+
`tests/test_call_status_conventions.py`, `test_untrusted_content_never_travels.py`,
|
|
120
|
+
`test_bom_shape.py`, `test_fingerprint_covers_what_the_analyzer_reads.py`. When
|
|
121
|
+
a review names a site, fix the invariant and extend the exhaustive test — a
|
|
122
|
+
second finding of the same class means the first fix was at the wrong level.
|
|
78
123
|
|
|
79
124
|
---
|
|
80
125
|
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: stacktrace-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Placeholder CLI for Stacktrace.ai — installs the `stacktrace` command.
|
|
5
|
+
Project-URL: Homepage, https://stacktrace.ai
|
|
6
|
+
Author-email: Prasanth G <prasanth@openaca.dev>
|
|
7
|
+
License-Expression: LicenseRef-Proprietary
|
|
8
|
+
Keywords: agent-security,ai-security,openaca,stacktrace
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: click>=8.1
|
|
17
|
+
Requires-Dist: httpx<1.0.dev0,>=0.28.1
|
|
18
|
+
Requires-Dist: openaca==0.6.0
|
|
19
|
+
Requires-Dist: openaidr==0.1.0
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# stacktrace-cli
|
|
23
|
+
|
|
24
|
+
Command-line interface for [Stacktrace.ai](https://stacktrace.ai).
|
|
25
|
+
|
|
26
|
+
The PyPI distribution is named `stacktrace-cli` Installing this package provides
|
|
27
|
+
the `stacktrace` executable.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install stacktrace-cli
|
|
31
|
+
stacktrace --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This is an early placeholder release. It depends on
|
|
35
|
+
[`openaca`](https://pypi.org/project/openaca/), the open-source Agent
|
|
36
|
+
Composition Analysis toolkit that Stacktrace.ai builds on.
|
|
37
|
+
|
|
38
|
+
## Design
|
|
39
|
+
|
|
40
|
+
This repository is the design home for Stacktrace Detect — the agentic AI
|
|
41
|
+
detection and response product this CLI grows into. OpenACA answers *what is
|
|
42
|
+
installed and what could it do*; Stacktrace Detect adds *what did it actually
|
|
43
|
+
do*, by joining agent session activity to the composition graph.
|
|
44
|
+
|
|
45
|
+
| Document | Covers |
|
|
46
|
+
|---|---|
|
|
47
|
+
| [docs/specs/aidr.md](docs/specs/aidr.md) | Umbrella — components, contracts, tenets, delivery |
|
|
48
|
+
| [docs/specs/session-input.md](docs/specs/session-input.md) | The seam over OpenAIDR, which collects sessions |
|
|
49
|
+
| [docs/specs/correlation.md](docs/specs/correlation.md) | The join: sessions against the composition graph |
|
|
50
|
+
| [docs/specs/detector.md](docs/specs/detector.md) | Three-stage detection and the finding family |
|
|
51
|
+
|
|
52
|
+
Decisions are in [docs/adrs/](docs/adrs/).
|
|
53
|
+
|
|
54
|
+
Built on two Apache-2.0 packages, neither of which depends on this one:
|
|
55
|
+
[`openaca`](https://pypi.org/project/openaca/) for composition analysis, and
|
|
56
|
+
[`openaidr`](https://github.com/open-agent-security/openaidr) for session
|
|
57
|
+
collection.
|
|
58
|
+
|
|
59
|
+
## Status
|
|
60
|
+
|
|
61
|
+
`stacktrace` is one front door over two kinds of command:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Analysis (OpenACA):
|
|
65
|
+
bom Generate an Agent BOM for a repository or endpoint.
|
|
66
|
+
policy Validate and compile restrictive endpoint policies.
|
|
67
|
+
scan Scan a repository or endpoint for agent-composition findings.
|
|
68
|
+
|
|
69
|
+
Stacktrace:
|
|
70
|
+
detect Find security and reliability findings in what agents did.
|
|
71
|
+
remote Configure remote endpoint services.
|
|
72
|
+
sessions Print what the agents on this machine actually did.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`stacktrace` is the entry point for hosted-product users. OpenACA remains the
|
|
76
|
+
tool itself and stays independently installable — with it installed, both
|
|
77
|
+
`openaca scan` and `stacktrace scan` work and do exactly the same thing,
|
|
78
|
+
because they are the same Click command object running in one process.
|
|
79
|
+
`stacktrace --version` reports both versions, which turns "which did you run?"
|
|
80
|
+
into one answered question.
|
|
81
|
+
|
|
82
|
+
**Pre-alpha, and not installable from PyPI yet.** `stacktrace sessions` works —
|
|
83
|
+
it reads what the agents on this machine did, through
|
|
84
|
+
[OpenAIDR](https://github.com/open-agent-security/openaidr):
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
$ uv run stacktrace sessions --since 2d --include-content
|
|
88
|
+
claude-code:s1 [claude-code] 2026-08-27T09:00:00+00:00 2 turns 2 calls
|
|
89
|
+
assistant: Reading the changelog before drafting the release notes.
|
|
90
|
+
ok 28c Read
|
|
91
|
+
result: ## 0.4.0 - correlate, detect
|
|
92
|
+
assistant: Filing the release-notes follow-up.
|
|
93
|
+
- github/create_issue
|
|
94
|
+
|
|
95
|
+
Summary — 1 sessions, 2 turns, 2 tool calls
|
|
96
|
+
|
|
97
|
+
agent kinds
|
|
98
|
+
1 claude-code
|
|
99
|
+
|
|
100
|
+
tools called (2 distinct)
|
|
101
|
+
1 Read
|
|
102
|
+
1 github/create_issue
|
|
103
|
+
|
|
104
|
+
MCP servers reached (1 distinct)
|
|
105
|
+
1 github
|
|
106
|
+
|
|
107
|
+
0 subagent turns · 0 results abridged upstream · 1 ok
|
|
108
|
+
|
|
109
|
+
1 of 2 calls returned with no outcome the collector could establish; the agent's parser supplies no success signal.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The blank status on the second row is the collector's `unknown`, not a pending
|
|
113
|
+
call: the client recorded no outcome it could establish, and the closing line
|
|
114
|
+
says how many of those there were rather than filling one in.
|
|
115
|
+
|
|
116
|
+
The next step is the join: resolving what ran to the components the agent is
|
|
117
|
+
built from. `detect` performs it — it correlates the collected sessions against
|
|
118
|
+
an Agent BOM before it judges anything — but there is no longer a command that
|
|
119
|
+
prints the correlated view on its own. `stacktrace correlate` was withdrawn; the
|
|
120
|
+
correlation code stays, as the stage `detect` is built on.
|
|
121
|
+
|
|
122
|
+
Both `openaca` and `openaidr` resolve from PyPI now, like any other
|
|
123
|
+
dependency — no sibling checkout, no `[tool.uv.sources]` override. What still
|
|
124
|
+
gates a release is narrower: both float on `>=` during normal development but
|
|
125
|
+
must be pinned exactly (`==`) before a version is published, so a built wheel
|
|
126
|
+
names precisely what it was tested against.
|
|
127
|
+
`uv run pytest tests/test_release_readiness.py -m release_gate` checks that —
|
|
128
|
+
the `release-stacktrace` skill runs it as part of cutting a release.
|
|
129
|
+
|
|
130
|
+
## Adding a command
|
|
131
|
+
|
|
132
|
+
A command is a **pass-through** or it is **native**, never both — and a
|
|
133
|
+
pass-through never gains a flag of its own. See
|
|
134
|
+
[ADR-0021](docs/adrs/0021-two-command-kinds.md) for why.
|
|
135
|
+
|
|
136
|
+
- **Pass-through** — add one string to `PASSTHROUGH` in
|
|
137
|
+
`src/stacktrace_cli/cli.py`, having decided it belongs. The loop registers
|
|
138
|
+
OpenACA's own command object under that name; there is nothing else to
|
|
139
|
+
write and nothing to keep in step with OpenACA.
|
|
140
|
+
- **Native** — write a Click command or group and `add_command` it, the way
|
|
141
|
+
`remote` is. If it needs OpenACA it calls `openaca.core`
|
|
142
|
+
([ADR-0020](docs/adrs/0020-openaca-consumption-boundary.md)), and whatever
|
|
143
|
+
it names there is added to the contract test in `tests/remote/`, because it
|
|
144
|
+
now holds duplicated knowledge of an interface.
|
|
145
|
+
|
|
146
|
+
The section a command lands under in `--help` follows from which kind it is;
|
|
147
|
+
there is no second list to update.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
`uv sync` and the four gates — `ruff check`, `ruff format --check`, `pyright`,
|
|
153
|
+
`pytest` — need nothing beside this repository. `openaca` resolves from PyPI at
|
|
154
|
+
the floor `pyproject.toml` declares. A sibling `../openaca` checkout is no
|
|
155
|
+
longer read: the interim `[tool.uv.sources]` path source retired at the 0.6.0
|
|
156
|
+
cutover.
|
|
157
|
+
|
|
158
|
+
## Cutover from `openaca remote`
|
|
159
|
+
|
|
160
|
+
`stacktrace remote` is the hosted-service client OpenACA is removing. The
|
|
161
|
+
behaviour, the payload and the Cloud endpoints are unchanged; only the code
|
|
162
|
+
that produces them moved. Machines already running `openaca remote sync
|
|
163
|
+
endpoint` need four steps each.
|
|
164
|
+
|
|
165
|
+
1. **Have the collector token to hand, or mint a new one.** A collector token
|
|
166
|
+
cannot be recovered from the Cloud — it is returned once at issuance and
|
|
167
|
+
only a hash and its last four characters are kept — so it has to come from
|
|
168
|
+
wherever it was stored (an MDM secret store, a password manager). If it was
|
|
169
|
+
not stored, mint a new one and deploy that; nothing goes down while you do,
|
|
170
|
+
because the existing token keeps working until it is revoked.
|
|
171
|
+
2. **Configure this machine.** `stacktrace remote configure --token …`, or set
|
|
172
|
+
`STACKTRACE_REMOTE_TOKEN` and `STACKTRACE_REMOTE_API_URL` for a scripted
|
|
173
|
+
deployment.
|
|
174
|
+
3. **Repoint whatever schedules the sync** at `stacktrace remote sync
|
|
175
|
+
endpoint`.
|
|
176
|
+
4. **Delete `~/.config/openaca/remote.toml`.** Once OpenACA has no `remote`
|
|
177
|
+
command, that file is an unused plaintext credential sitting on disk.
|
|
178
|
+
Nothing breaks if it stays, which is exactly why it will be forgotten.
|
|
179
|
+
|
|
180
|
+
### What takes care of itself
|
|
181
|
+
|
|
182
|
+
- **The asset converges.** Registration is idempotent on `(org, asset_type,
|
|
183
|
+
external_id)` with the host name as `external_id`, so registering from here
|
|
184
|
+
resolves to the same asset OpenACA registered — one extra round trip on the
|
|
185
|
+
first run, no duplicate machine in the console.
|
|
186
|
+
- **Both commands may coexist during the overlap**, producing two BOM rows per
|
|
187
|
+
sync. That is noise rather than corruption.
|
|
188
|
+
|
|
189
|
+
> These two statements, and the unrecoverability of a collector token in step
|
|
190
|
+
> 1, describe what the deployed Cloud does. They have **not** been confirmed
|
|
191
|
+
> against the running service for this release — they are read from the
|
|
192
|
+
> hosted side's design, which records what some revision implements rather
|
|
193
|
+
> than what production does today. Confirm them before an operator acts on
|
|
194
|
+
> step 1 in particular: discarding the only copy of a token on the strength of
|
|
195
|
+
> an unverified sentence leaves no recovery.
|
|
196
|
+
|
|
197
|
+
### What does not
|
|
198
|
+
|
|
199
|
+
- **A pending OpenACA spool is orphaned.** Its files are in
|
|
200
|
+
`~/.local/state/openaca`, and this channel never reads them. Drain it by
|
|
201
|
+
running the old command once before cutting over, or accept losing what it
|
|
202
|
+
holds. Do not assume that is one sync's worth: the spool keeps one file per
|
|
203
|
+
failed agent and accumulates across offline runs, so count the files first.
|
|
204
|
+
- **The MDM deploy scripts and the scheduled agent** do not exist here yet.
|
|
205
|
+
|
|
206
|
+
## Release obligations
|
|
207
|
+
|
|
208
|
+
Finishing the remote-sync work was **not** the same as being releasable, and
|
|
209
|
+
the gap was structural rather than a matter of polish: while `pyproject.toml`
|
|
210
|
+
carried a path source to a sibling checkout, `pip install stacktrace-cli` from
|
|
211
|
+
PyPI would have resolved an `openaca` without the consumption facade, and every
|
|
212
|
+
`remote` command would have failed at import.
|
|
213
|
+
|
|
214
|
+
All three are discharged, at `openaca` 0.6.0 — the first release carrying the
|
|
215
|
+
facade:
|
|
216
|
+
|
|
217
|
+
1. `[tool.uv.sources]` is gone; `uv.lock` resolves `openaca` from PyPI.
|
|
218
|
+
2. `.github/workflows/ci.yml` checks out this repository alone. The sibling
|
|
219
|
+
checkout and its pinned-revision assertion went with the path source.
|
|
220
|
+
3. The floor is `openaca>=0.6.0`, which is where plan 005 Task 1's
|
|
221
|
+
required-import probe first reports no missing name. Re-run that probe
|
|
222
|
+
against any candidate release before moving the floor again; it passes only
|
|
223
|
+
when it reports nothing missing.
|
|
224
|
+
|
|
225
|
+
One proof retired with them. The payload-equivalence test imported openaca's
|
|
226
|
+
`tools.remote` — the module 0.6.0 removed — so it cannot run against any
|
|
227
|
+
release that carries the facade. What it established is a fact about the
|
|
228
|
+
migration, not a property that a later release could re-check.
|