mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(pre-push-safety-net): measure frozen PMPro and Node replay feasibility
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Brief: pre-push-safety-net
|
||||
|
||||
Status: requirements, design and tasks approved; groups 1–4 verified complete (14/27 subtasks). Groups 5–8 pending; real capability eligibility and hook latency remain unproven. Language: English.
|
||||
Status: requirements, design and tasks approved; groups 1–5 implemented and verified (16/27 subtasks). The first PMPro/Node experiment is measured NO-GO: six cold timeouts, no completed target checks. Groups 6–8 pending; normal capabilities remain disabled and warm hook latency unproven. Language: English.
|
||||
|
||||
## Problem
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Implementation verification: group 5
|
||||
|
||||
Group 5 was authorized by the maintainer's “approved” after group 4 completion.
|
||||
Task 5.1 is verified complete (15/27 total); task 5.2 is next.
|
||||
Tasks 5.1–5.2 are implemented and verified (16/27 total); the measured feasibility decision is NO-GO.
|
||||
Real capability eligibility and rollout readiness remain unproven.
|
||||
|
||||
## Execution and review mode
|
||||
@@ -105,3 +105,80 @@ facts or detector semantics changed. The additional regression failed before rep
|
||||
- CLAIM: The reproduced Node cancellation path retains diagnostics and returns within 30 + 2 seconds.
|
||||
- EVIDENCE: Final packaged real Node replay returned in 30.1255 seconds with one complete diagnostic artifact and incomplete coverage. Final policy/core/facts/query hashes match the workspace. See `benchmarks/measurements/2026-09-13-replay-deadline-repair.json` for initial/partial-repair traces and final raw artifact.
|
||||
- GAPS: This proves the reproduced cancellation path, not representative warm performance or security coverage. Normal capabilities remain disabled.
|
||||
|
||||
## Task 5.2 brief and measurement method
|
||||
|
||||
Requirements 6.2–6.5 and 8.1–8.4: execute the prepared PMPro and Node security/fixed/benign
|
||||
changes through full replay, freeze the core before transfer, retain unresolved populations,
|
||||
and make an explicit feasibility decision. This task owns the evaluation harness, not new
|
||||
engine abstractions, framework facts, ranking weights or normal alert eligibility.
|
||||
|
||||
The harness freezes the full input population and runtime profile before invoking the CLI.
|
||||
Authored variants are reproducible commits in a private Git object store, with byte/hash
|
||||
checks and the rest of each tree retained. The original cache is mounted read-only. All six
|
||||
cases run under 30 seconds plus a 2-second cancellation/report allowance, with complete CLI
|
||||
elapsed time measured. Exact artifacts/process exits remain separate from the scorer's
|
||||
witness association. Empty completed rows cannot manufacture a witnessed negative.
|
||||
|
||||
Initial OFF-state evidence: `/tmp/ousast-experiment-red.log`, three failed acceptance tests.
|
||||
After implementation and flag removal, the same three tests pass, including whole-tree
|
||||
retention, unchanged source/index, reproducible authored identity, bad-hash refusal,
|
||||
timeout accounting and absence of fabricated negative evidence.
|
||||
|
||||
The first v1 trial is retained as diagnostic history. Its Node post-deadline failures
|
||||
triggered the predecessor repair above; PMPro measurements overlapped regression tests.
|
||||
The v2 profile is frozen anew after repair; final timed runs have no competing test/build
|
||||
workload from this session. Both profiles and their distinct core/policy hashes are retained.
|
||||
The reserved Ghost workload remains untouched. No framework/fact adaptation or ranking
|
||||
retune is made from these Node results.
|
||||
|
||||
## Final experiment: measured NO-GO
|
||||
|
||||
[Joint scorecard](../../../benchmarks/measurements/2026-09-13-first-replay-experiment.json)
|
||||
records the final v2 profile, all metrics, hardware, exact case identities, initial trial
|
||||
and final-trial bundle hashes. The two gzip bundles are JSON mappings from each original
|
||||
filename to its exact UTF-8 contents; decompression was checked against every original file.
|
||||
They preserve all detailed CLI artifacts, stdout/stderr, pins, profiles and scorer inputs.
|
||||
|
||||
| Workload | Cases | Whole CLI elapsed | Reached analysis | Result |
|
||||
|---|---:|---|---|---|
|
||||
| PMPro | 3 | 30.61–30.64 s | No; both trees materialized, discovery/preparation expired before change context | Timeout, no completed target check |
|
||||
| NodeGoat | 3 | 30.55–30.58 s | Driver reached after 7.78–7.96 s preparation; deadline consumed in head analysis/planning | Timeout, 436 deferred questions per case |
|
||||
|
||||
All six processes exited 0 (advisory allow), saved artifacts and explicitly reported
|
||||
incomplete analysis. The harness exited **1 with a completed NO-GO decision**, distinct
|
||||
from an instrument crash. Both the initial and post-PMPro freeze match each final runtime
|
||||
and the current workspace's core/facts/query/policy hashes. CPU: Intel Core i7-8700,
|
||||
four exposed CPUs, 3 GiB container memory limit, network disabled. Tests/builds were not
|
||||
running during these final measurements. Filesystem caches were not flushed.
|
||||
|
||||
The scorer retains all 11 unresolved cases; six were executed and the regression,
|
||||
unsupported-envelope and reserved populations were not scanned. No security change was
|
||||
recovered within budget, no witnessed fixed-side negative was established, and no normal
|
||||
alert was admitted. Precision has denominator zero and is undefined. The two benign
|
||||
pushes emitted no alert, but an empty registry cannot establish product usefulness.
|
||||
There are no warm-changed or identical-tip samples. Cold sample p50 is 30.58 s and p95
|
||||
30.64 s for six one-shot deadline-limited runs; these are not warm latency acceptance evidence.
|
||||
|
||||
The negative result points first to preparation/discovery cost for PMPro, before graph
|
||||
persistence can help that path. Group6 can implement valid reuse, including compatible
|
||||
preparation work, but must not claim scale from identical-tip hits or bypass the ranker
|
||||
with another slicer. No new abstract capability or incremental graph mutation is justified
|
||||
by this measurement. Normal capability qualification remains task8.3.
|
||||
|
||||
## Task 5.2 review verdict
|
||||
- VERDICT: APPROVED
|
||||
- TASK: 5.2
|
||||
- MECHANICAL_RESULTS: Full suite 1,242 passed, nine skipped, exit 0; Ruff/format (243 files), mypy (112 source files), compileall and detection/map/pair gates PASS; three behavioral OFF-state failures reproduced before implementation; no new placeholder markers/secrets; evaluation boundary respected.
|
||||
- FINDINGS: Initial instrument failures repaired in their owning driver/replay integration before the final freeze. All six final runtime provenance records match the frozen profile; scorer replay and complete compressed-artifact round trips reproduce exactly. No outstanding implementation blocker.
|
||||
- SUMMARY: Manual review approves the executed diagnostic experiment and truthful NO-GO assessment; it does not approve usefulness, warm latency or capability admission.
|
||||
|
||||
## Group 5 verification and integration assessment
|
||||
- STATUS: VERIFIED
|
||||
- CLAIM_TYPE: TASK
|
||||
- CLAIM: Tasks5.1–5.2 implement full local replay and execute the first frozen PMPro/Node experiment with truthful evidence, failures, deadline accounting and an explicit feasibility decision.
|
||||
- EVIDENCE: Controlled security/fixed replay, packaged deadline repair, 1,242 passing tests, fresh static/regression checks, six final real CLI replays, exact frozen runtime hashes and reproducible 11-case scorecard. Task5.2 acceptance requires publishing the actual decision; this decision is NO-GO.
|
||||
- GAPS: No completed target checks in the 30-second cold workload, no warm measurements, no independent eligibility, and remaining groups6–8. The first usefulness/quality/latency milestone has not passed; the feature is not complete.
|
||||
|
||||
Implementation and measurement integration: GO for the scoped work in group5.
|
||||
Product feasibility and rollout: **NO-GO**. Kiro state is 16/27 tasks verified; group6 next.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"feature_name": "pre-push-safety-net",
|
||||
"created_at": "2026-09-12T15:35:00Z",
|
||||
"updated_at": "2026-09-13T13:11:42.850127+00:00",
|
||||
"updated_at": "2026-09-13T13:22:55.634332+00:00",
|
||||
"language": "en",
|
||||
"phase": "implementation",
|
||||
"approvals": {
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
},
|
||||
"ready_for_implementation": true,
|
||||
"implementation_approval_note": "Group5 authorized. Task5.1 and its observed deadline regression repaired and verified with manual Kiro fallback; task5.2 resumes under a newly frozen core identity.",
|
||||
"implementation_approval_note": "Groups1\u20135 implemented and verified (16/27). Group5 used manual Kiro fallback review, repaired observed predecessor deadline bugs, and executed a newly frozen PMPro/Node experiment. Measured feasibility NO-GO; real capabilities remain disabled. Groups6\u20138 pending.",
|
||||
"depends_on": [
|
||||
"contributor-scan"
|
||||
],
|
||||
@@ -92,9 +92,10 @@
|
||||
"4.1",
|
||||
"4.2",
|
||||
"4.3",
|
||||
"5.1"
|
||||
"5.1",
|
||||
"5.2"
|
||||
],
|
||||
"remaining_tasks": 12,
|
||||
"remaining_tasks": 11,
|
||||
"verification_record": "implementation-group-5.md",
|
||||
"verification_records": [
|
||||
"implementation-group-1.md",
|
||||
@@ -103,7 +104,8 @@
|
||||
"implementation-group-4.md",
|
||||
"implementation-group-5.md"
|
||||
],
|
||||
"next_group": 5,
|
||||
"blocked_tasks": []
|
||||
"next_group": 6,
|
||||
"blocked_tasks": [],
|
||||
"feasibility": "NO-GO: six cold replay timeouts; no completed supported target checks; warm performance and eligibility unproven"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Implementation Plan: pre-push-safety-net
|
||||
|
||||
Status: task plan approved by the maintainer on 2026-09-12. Groups 1–4 (tasks 1.1–4.3)
|
||||
are complete after task reviews and fresh verification (14/27 subtasks). Task 4.3 used
|
||||
the documented manual review fallback after agent dispatch became unavailable.
|
||||
Task 5.1 is also verified (15/27 total); task 5.2 and groups 6–8 remain pending; no production capability or hook latency is admitted.
|
||||
Status: task plan approved by the maintainer on 2026-09-12. Groups 1–5 (tasks 1.1–5.2)
|
||||
are implemented, reviewed and verified (16/27 subtasks). Tasks 4.3 and group 5 used the
|
||||
documented Kiro manual review fallback after agent dispatch reached its thread limit.
|
||||
The first experiment is measured NO-GO: all six cold PMPro/Node cases timed out without
|
||||
completed target checks. Groups 6–8 remain pending; no production capability or warm
|
||||
hook latency is admitted.
|
||||
|
||||
## Execution contract
|
||||
|
||||
@@ -139,7 +141,7 @@ Task 5.1 is also verified (15/27 total); task 5.2 and groups 6–8 remain pendin
|
||||
- _Boundary: Result and Hook Adapter_
|
||||
- _Requirements: 3.1, 3.2, 3.3, 5.1, 5.2, 5.5, 7.3_
|
||||
|
||||
- [ ] 5. Prove the first end-to-end experiment before cache optimization
|
||||
- [x] 5. Prove the first end-to-end experiment before cache optimization
|
||||
- [x] 5.1 Wire explicit base/head replay through the full transaction
|
||||
- Connect immutable snapshots, ranker decisions, existing arbiter, targeted comparison, admission diagnostics and compact artifacts through an explicit local replay command.
|
||||
- Apply the shared budget to preparation and both revisions; no model/service or installed hook is required. All runtime errors preserve coverage state.
|
||||
@@ -148,11 +150,12 @@ Task 5.1 is also verified (15/27 total); task 5.2 and groups 6–8 remain pendin
|
||||
- _Boundary: Push runner, CLI, Snapshot Adapter, engine and policy integration_
|
||||
- _Depends: 1.3, 3.1, 3.4, 4.3_
|
||||
- _Requirements: 1.1, 2.1, 3.1, 3.4, 4.1, 4.2, 5.5, 7.1, 7.3_
|
||||
- [ ] 5.2 Measure PMPro and frozen-core Node transfer
|
||||
- [x] 5.2 Measure PMPro and frozen-core Node transfer
|
||||
- _Prerequisite repaired 2026-09-13:_ 3.1/5.1 deadline regression verified in the packaged Node replay; re-freeze the experiment after this core identity change.
|
||||
- Replay each prepared security/fixed/benign case, first using the declared hook deadline; record timeouts and stage costs. A separately named extended lab run may obtain witnesses but cannot satisfy the hook latency gate.
|
||||
- Freeze the core after PMPro, run Node with existing facts, and classify instrument, mapping, fact, scope and domain gaps without tuning on an untouched holdout.
|
||||
- Done when one joint artifact reports actual evidence and outcomes for both workloads, with unresolved cases retained and an explicit feasibility decision; no normal capability is enabled here.
|
||||
- _Verified 2026-09-13:_ manual Kiro review APPROVED; six final CLI cases measured under unchanged core, all timed out in 30.55–30.64 seconds with artifacts retained. Full 11-case population retained; feasibility NO-GO, no eligibility enabled. 1,242 tests passed, nine skipped. See `implementation-group-5.md`.
|
||||
- _Boundary: Evaluation harness, full-run integration_
|
||||
- _Depends: 1.2, 1.3, 5.1_
|
||||
- _Requirements: 6.2, 6.3, 6.4, 6.5, 8.1, 8.2, 8.3, 8.4_
|
||||
@@ -234,3 +237,9 @@ Task 5.1 is also verified (15/27 total); task 5.2 and groups 6–8 remain pendin
|
||||
- Done when a reproducible validation artifact states GO only with all mandatory integration and joint quality/latency gates satisfied; otherwise it names the remaining NO-GO conditions without marking the feature complete.
|
||||
- _Boundary: Packaged runtime, full feature validation integration_
|
||||
- _Requirements: 3.1, 3.4, 5.1, 5.2, 5.3, 5.4, 5.5, 6.5, 7.1, 7.3, 7.4, 8.3, 8.4_
|
||||
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Task5.2 measured PMPro timing out after both snapshots were materialized but before change context/graph analysis. Group6 must account for compatible snapshot/discovery reuse as well as graph reuse; identical-tip hits cannot substitute for the later representative changed-code latency gate. No new detector, scope heuristic or incremental CPG mutation is implied.
|
||||
- Cancellation must cover semantic planning and reporting, not only engine subprocesses: `_collect` and `_scope_work` now consume the shared deadline, and replay instantiates the budget-aware backend once. Preserve the 400-question failure-census and real Node artifact-retention regressions.
|
||||
|
||||
+24
-19
@@ -46,28 +46,33 @@ engine are out of scope.
|
||||
- [ ] pre-push-safety-net — Immutable pushed tips, existing-ranker integration, compatible artifact
|
||||
reuse, total deadline, change attribution, actionability admission and compact hook behavior.
|
||||
Dependencies: contributor-scan. Requirements/design approved 2026-09-12; tasks generated
|
||||
(8 groups, 27 executable subtasks), independently reviewed and approved; groups 1–4 are complete (14/27 tasks), including the resolved frontend-retention prerequisite.
|
||||
(8 groups, 27 executable subtasks), independently reviewed and approved; groups 1–5 are implemented and verified (16/27 tasks), including the resolved frontend-retention prerequisite.
|
||||
|
||||
### Next concrete step
|
||||
|
||||
Groups 1–4 are verified: runtime, inputs, scoring, snapshots, shared deadline,
|
||||
ranker scope, change context, vendor-free partitions, novelty comparison, actionability
|
||||
admission and compact reporting. Tasks 4.1/4.2 passed independent review; 4.3 used the
|
||||
Kiro manual review fallback after the agent service reached its thread limit.
|
||||
Group 5 is next: wire explicit full transaction replay, then run the first bounded
|
||||
experiment. Real capabilities remain disabled pending independent evaluation.
|
||||
Automatic dominance-only change-context projection remains an upstream limit to
|
||||
retain during replay integration. See `pre-push-safety-net/implementation-group-4.md`
|
||||
for verification and scope limits.
|
||||
The first complete experiment is 5.2, before persistent caching and hook
|
||||
installation work. Keep PMPro as the primary core-optimization example, then freeze the core and
|
||||
prove a changed security flow and its fixed twin on a JavaScript/Express Node API workload through
|
||||
the same ranker, with a real deadline and vendor-free graph. Include benign pushes and retain Python
|
||||
regressions. `pre-push-safety-net/evaluation.md` proposes NodeGoat for integration, a separate untouched
|
||||
Node API repository for generalization, and libpng for C envelope/abstraction-limit evidence. Shared
|
||||
abstract evidence and decisions are mandatory; framework facts/adaptations are reported separately.
|
||||
Measure realistic changed-code cache behavior before more ranker tuning. Broader runtime and
|
||||
capability admission follow that result; an LLM ranker is optional.
|
||||
Groups 1–5 now connect immutable replay to the existing ranker, detector, comparison,
|
||||
admission and compact artifacts. The first frozen PMPro/Node experiment is **NO-GO**:
|
||||
all six cold security/fixed/benign cases timed out without completed target checks.
|
||||
After repairing post-deadline semantic planning and a repeated availability probe,
|
||||
every final CLI run saved its artifact and returned in 30.55–30.64 seconds. PMPro spent
|
||||
its deadline in preparation/discovery after materializing both trees; Node reached the
|
||||
driver but did not complete checks. The exact 11-case population remains unresolved.
|
||||
No normal capability is enabled and these six cold samples establish no warm p95 claim.
|
||||
|
||||
Group 6 is next for valid graph/result identity, leases and reuse. Account for snapshot
|
||||
and discovery costs before treating graph persistence as the sole optimization; record
|
||||
compatible preparation reuse alongside graph reuse. Representative changed-code warm
|
||||
latency and coverage must still be measured later, without substituting identical-tip hits.
|
||||
Do not change ranking weights, add a second source-slicer or assume incremental Joern
|
||||
mutation from this negative performance evidence.
|
||||
|
||||
Tasks 4.3 and group 5 used the documented Kiro manual review fallback after the agent
|
||||
thread limit; this is not independent review. See `pre-push-safety-net/implementation-group-5.md`
|
||||
and `benchmarks/measurements/2026-09-13-first-replay-experiment.json` for provenance,
|
||||
initial instrument failures, their repairs and the newly frozen final trial. The NodeGoat
|
||||
teaching workload did not cause a framework/fact adaptation or ranking retune. Ghost remains
|
||||
untouched; independent PHP/Python eligibility populations and C/C++ property support remain
|
||||
separate gaps. Automatic dominance-only context projection is still an upstream limit.
|
||||
|
||||
## Historical roadmap narrative — September 9–11
|
||||
|
||||
|
||||
@@ -79,11 +79,16 @@ updates as a shortcut to an unmeasured latency promise.
|
||||
The product direction above is user-requested. The amended requirements, design and initial
|
||||
evaluation profile in `pre-push-safety-net` were approved for task generation on 2026-09-12
|
||||
("ok, next kiro phase"). The maintainer then approved the task plan and starting group 1
|
||||
("approve.") and then group 2 ("ok, next kiro phase, approved"). Groups 1–4 are now verified
|
||||
complete (14/27 total), including the approved frontend-retention prerequisite.
|
||||
Group 4 adds bound novelty, operation-specific admission and compact artifacts;
|
||||
4.1/4.2 passed independent review and 4.3 used the documented Kiro manual fallback
|
||||
after agent dispatch became unavailable. Group 5 is next for full replay and the
|
||||
first bounded experiment. Task completion still requires review and fresh evidence.
|
||||
Real capability eligibility, automatic dominance context projection and hook
|
||||
performance remain unproven; the default capability registry is empty.
|
||||
("approve.") and then group 2 ("ok, next kiro phase, approved"). Groups 1–5 are now
|
||||
implemented, reviewed and verified (16/27), including the frontend-retention prerequisite
|
||||
and the deadline regression exposed by full Node replay. Tasks 4.3 and group 5 used the
|
||||
explicit Kiro manual review fallback after the agent thread limit.
|
||||
|
||||
The first newly frozen PMPro/Node experiment is measured **NO-GO**: all six cold cases
|
||||
timed out without completed supported target checks, while retaining all 11 declared cases.
|
||||
The repaired CLI saved all artifacts and returned in 30.55–30.64 seconds. PMPro exhausted
|
||||
the deadline in preparation/discovery before graph analysis, so compatible preparation
|
||||
reuse matters alongside graph persistence. Group 6 is next; warm changed-code performance
|
||||
and useful coverage remain mandatory later gates. No ranking/fact adaptation or holdout
|
||||
inspection occurred in the final transfer run. Real capability eligibility and automatic
|
||||
dominance context projection remain unproven; the default capability registry is empty.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -89,3 +89,51 @@ results, per-capability metrics, coverage and cold/warm latency. Missing benign
|
||||
have unknown interruption rate. VAmPI's readable regression may be measured while
|
||||
independent admission prerequisites remain missing. Unsupported/unreviewed populations
|
||||
stay visible. All results remain experimental pending the later joint admission gate.
|
||||
|
||||
## First full replay experiment (task 5.2)
|
||||
|
||||
`measure.py` runs the six prepared PMPro/NodeGoat security, fixed and benign cases
|
||||
through the production `ousast pre-push` CLI. It does not supply a hand-selected
|
||||
source slice or regions. The complete 11-case population remains in the scorer;
|
||||
Ghost is not scanned, and the separate VAmPI/libpng populations are retained as
|
||||
unexecuted regression/envelope evidence rather than being silently dropped.
|
||||
|
||||
Build the local image first, then run with a writable output parent and the existing
|
||||
repository-object cache mounted read-only:
|
||||
|
||||
```sh
|
||||
docker run --rm --network none --memory 3g --entrypoint python \
|
||||
-v "$PWD:/context:ro" \
|
||||
-v "$HOME/.cache/openultrasast/repos:/cache:ro" \
|
||||
-v /path/to/output-parent:/results \
|
||||
openultrasast:dev /context/benchmarks/push/measure.py \
|
||||
--manifest /context/benchmarks/push/inputs.json --cache /cache --out /results/new-run
|
||||
```
|
||||
|
||||
The output directory must not already exist. Input validation opens and verifies
|
||||
pinned source/license blobs before any measurement. A private bare Git repository
|
||||
reads the existing objects and creates reproducible authored commits using only the
|
||||
specified edits. Every other tracked blob remains in the tree; the cache's files,
|
||||
index and refs are unchanged. No project code, package installation or network is used.
|
||||
|
||||
`profile.json` is written before the first replay. PMPro runs first, followed by an
|
||||
explicit `freeze-after-pmpro.json` checkpoint and Node with identical installed core,
|
||||
facts, queries and policy. Both analysis and cancellation allowances stay 30 and 2
|
||||
seconds. Whole CLI elapsed time, separate available stage costs, exact input pins,
|
||||
source byte counts, selected/deferred identities, raw artifacts, process exits and
|
||||
terminal output remain available. `rank` denotes an observed selected-question position;
|
||||
an unlocated/unselected target retains null. A completed empty query cannot earn a
|
||||
witnessed negative or fixed-side silence. No transitive path is fabricated from rank.
|
||||
|
||||
`scorecard.json` uses the existing frozen-population scorer. `decision.json` explicitly
|
||||
returns NO-GO because this initial cold diagnostic does not qualify independent
|
||||
capabilities or prove joint quality and warm latency. Exit **1** with a completed
|
||||
`decision.json` means measured NO-GO; a traceback without that completion record is a
|
||||
failed instrument. Zero CLI exit means advisory allow, never complete analysis.
|
||||
Missing artifacts and unanswered questions remain errors/unresolved, not clean results.
|
||||
|
||||
Cold here means no persisted graph/query/comparison reuse; filesystem caches are not
|
||||
flushed. Six one-shot runs are diagnostic samples, not representative warm p95 evidence.
|
||||
Normal capabilities remain disabled, so zero alerts do not establish precision or useful
|
||||
recall. A separately named longer lab run may obtain witnesses, but cannot replace the
|
||||
30-second measurements or satisfy the hook's latency gate.
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
"""First frozen-core PMPro/Node diagnostic experiment, using local Git objects only."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from openultrasast.model.contracts import QuestionIdentity
|
||||
from openultrasast.model.scan import ScanBudget
|
||||
from openultrasast.push.runner import _provenance
|
||||
from openultrasast.push_inputs import validate_manifest
|
||||
from openultrasast.push_scoring import freeze_profile, score
|
||||
from openultrasast.repos import checkout_path, load_repo_recipe
|
||||
|
||||
|
||||
def git(root: Path, *args: str, data: bytes | None = None) -> bytes:
|
||||
env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")}
|
||||
env.update(
|
||||
GIT_CONFIG_GLOBAL=os.devnull,
|
||||
GIT_CONFIG_NOSYSTEM="1",
|
||||
GIT_NO_REPLACE_OBJECTS="1",
|
||||
GIT_NO_LAZY_FETCH="1",
|
||||
GIT_ALLOW_PROTOCOL="",
|
||||
GIT_GRAFT_FILE=os.devnull,
|
||||
GIT_AUTHOR_NAME="Authored evaluation",
|
||||
GIT_AUTHOR_EMAIL="[email protected]",
|
||||
GIT_COMMITTER_NAME="Authored evaluation",
|
||||
GIT_COMMITTER_EMAIL="[email protected]",
|
||||
GIT_AUTHOR_DATE="2026-09-12T00:00:00+00:00",
|
||||
GIT_COMMITTER_DATE="2026-09-12T00:00:00+00:00",
|
||||
)
|
||||
return subprocess.run(
|
||||
["git", "-c", "core.hooksPath=" + os.devnull, "-C", str(root), *args],
|
||||
input=data,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
timeout=30,
|
||||
env=env,
|
||||
).stdout
|
||||
|
||||
|
||||
def pin_snapshot(bare: Path, commit: str, snapshot: dict) -> str:
|
||||
"""Apply declared edits to a private Git index; retain every other tracked blob."""
|
||||
git(bare, "read-tree", commit)
|
||||
edited = False
|
||||
for entry in snapshot["files"]:
|
||||
content = git(bare, "cat-file", "blob", commit + ":" + entry["path"])
|
||||
if not content:
|
||||
raise ValueError("empty input bytes")
|
||||
if edit := entry.get("edit"):
|
||||
before, after = edit["before"].encode(), edit["after"].encode()
|
||||
if not edit["provenance"].startswith("authored") or not before or content.count(before) != 1:
|
||||
raise ValueError("authored edit must match exactly once")
|
||||
if hashlib.sha256(content).hexdigest() != entry["original_sha256"]:
|
||||
raise ValueError("original hash mismatch")
|
||||
content = content.replace(before, after, 1)
|
||||
edited = True
|
||||
if len(content) != entry["size"] or hashlib.sha256(content).hexdigest() != entry["sha256"]:
|
||||
raise ValueError("resulting input identity mismatch")
|
||||
if entry.get("edit"):
|
||||
blob = git(bare, "hash-object", "-w", "--stdin", data=content).decode().strip()
|
||||
mode = git(bare, "ls-tree", commit, "--", entry["path"]).split(b" ", 1)[0].decode()
|
||||
git(bare, "update-index", "--add", "--cacheinfo", mode, blob, entry["path"])
|
||||
if not edited:
|
||||
return commit
|
||||
tree = git(bare, "write-tree").decode().strip()
|
||||
return (
|
||||
git(bare, "commit-tree", tree, "-p", commit, data=("Authored evaluation snapshot: " + snapshot["id"] + "\n").encode())
|
||||
.decode()
|
||||
.strip()
|
||||
)
|
||||
|
||||
|
||||
def execution_record(case_id: str, artifact: dict, *, elapsed: float, target: dict | None = None) -> dict:
|
||||
"""Associate existing answers; empty rows cannot manufacture a witnessed negative."""
|
||||
selected, deferred, queries = [], [], []
|
||||
rank = None
|
||||
modes = set()
|
||||
for record in artifact["scans"]:
|
||||
if record["side"] != "head":
|
||||
continue
|
||||
scan = record["scan"]
|
||||
if scope := scan.get("scope"):
|
||||
modes.add(scope["ranking_mode"])
|
||||
if target is not None:
|
||||
rank = next(
|
||||
(
|
||||
index
|
||||
for index, q in enumerate(scope["selected"], 1)
|
||||
if all(q["identity"][key] == target[key] for key in ("path", "function", "family"))
|
||||
),
|
||||
None,
|
||||
)
|
||||
selected.extend(QuestionIdentity.from_payload(q["identity"]).question_id for q in scope["selected"])
|
||||
deferred.extend(QuestionIdentity.from_payload(q["identity"]).question_id for q in scope["deferred"])
|
||||
for outcome in scan["question_outcomes"]:
|
||||
identity = QuestionIdentity.from_payload(outcome["identity"])
|
||||
query = {
|
||||
"id": identity.question_id,
|
||||
"status": "answered" if outcome["status"] == "completed" else "unanswered",
|
||||
"outcome": "unknown",
|
||||
}
|
||||
# Keep the exact question/answer association. A raw finding reached from
|
||||
# another function is not a proved transitive path for this scorer.
|
||||
findings = [
|
||||
f
|
||||
for f in scan["findings"]
|
||||
if f["family"] == identity.family
|
||||
and f["site"].startswith(identity.path + ":")
|
||||
and f["site"].rsplit(":", 1)[-1] == identity.function
|
||||
]
|
||||
if outcome["status"] == "completed" and findings:
|
||||
target_sites = {f"{target['path']}:{line}:{target['function']}" for line in target["lines"]} if target else set()
|
||||
finding = next((f for f in findings if f["site"] in target_sites), findings[0])
|
||||
query.update(outcome="positive", finding={**finding, "site": finding["site"].rsplit(":", 1)[0]})
|
||||
queries.append(query)
|
||||
if modes and modes != {"evidence"}:
|
||||
raise ValueError("runtime ranker differs from frozen evidence mode")
|
||||
deltas = [d["candidate"]["delta"]["novelty"] for d in artifact["admission"]["dispositions"]]
|
||||
novelty = next((d for d in ("new", "worsened", "unknown", "unchanged") if d in deltas), "unknown")
|
||||
reasons = artifact["admission"]["coverage_reasons"]
|
||||
coverage = (
|
||||
"timeout"
|
||||
if any("deadline" in reason for reason in reasons)
|
||||
else ("complete" if artifact["result"]["coverage_status"] == "complete_within_scope" else "incomplete")
|
||||
)
|
||||
if artifact["admission"]["defects"]:
|
||||
raise ValueError("unqualified experiment unexpectedly emitted an admitted alert")
|
||||
return {
|
||||
"case_id": case_id,
|
||||
"ranking_mode": "evidence",
|
||||
"rank": rank,
|
||||
"rank_kind": "selected_question_position",
|
||||
"selected": selected,
|
||||
"deferred": deferred,
|
||||
"queries": queries,
|
||||
"alerts": [],
|
||||
"delta": novelty,
|
||||
"coverage": coverage,
|
||||
"cache_state": "cold",
|
||||
"timings": {
|
||||
"total": elapsed,
|
||||
"build": sum(r["scan"].get("build_seconds", 0) for r in artifact["scans"]),
|
||||
"query": sum(r["scan"].get("query_seconds", 0) for r in artifact["scans"]),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def write(path: Path, data: dict) -> None:
|
||||
path.write_text(json.dumps(data, indent=2, allow_nan=False) + "\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--manifest", type=Path, required=True)
|
||||
parser.add_argument("--cache", type=Path, required=True)
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
args.out.mkdir(parents=True, exist_ok=False)
|
||||
manifest = json.loads(args.manifest.read_bytes())
|
||||
validation = validate_manifest(args.manifest, cache_root=args.cache, fetch=False)
|
||||
write(args.out / "input-validation.json", validation)
|
||||
settings = ScanBudget(max_model_calls=0, max_regions=500, order_by_evidence=True)
|
||||
initial = _provenance(args.manifest.parent, settings)
|
||||
keys = ("ranking_mode", "engine", "facts", "queries", "policy", "core")
|
||||
config = {key: initial[key] for key in keys}
|
||||
config.update(
|
||||
version="first-replay-v2-2026-09-13",
|
||||
scope="full tracked tree; declared first-party graph/target exclusions; evidence ranker",
|
||||
hardware=platform.platform(),
|
||||
budget_seconds=30,
|
||||
targets=json.loads((args.manifest.parent / "diagnostic-config-v1.json").read_text())["targets"],
|
||||
)
|
||||
profile = freeze_profile(args.manifest.read_bytes(), config)
|
||||
write(args.out / "profile.json", profile) # Freeze on disk BEFORE any measurement.
|
||||
run = {"profile_sha256": profile["profile_sha256"], "context": dict(config), "records": []}
|
||||
evidence = {"cases": [], "core_changes": [], "adapter_changes": [], "freeze_after_pmpro": None}
|
||||
ready = {c["id"]: c["status"] == "ready" for c in validation["cases"]}
|
||||
snapshots = {s["id"]: s for s in manifest["snapshots"]}
|
||||
# Fixed declared order; reserved, regression and unsupported populations stay
|
||||
# in the joint scorer without scanning or tuning on the holdout.
|
||||
cases = [c for prefix in ("pmpro-", "nodegoat-") for c in manifest["cases"] if c["id"].startswith(prefix)]
|
||||
with tempfile.TemporaryDirectory(prefix="ousast-experiment-") as temporary:
|
||||
bare = Path(temporary) / "objects.git"
|
||||
bare.mkdir()
|
||||
git(bare, "init", "--bare")
|
||||
sources = {}
|
||||
for case in cases:
|
||||
for sid in (case["base"], case["tip"]):
|
||||
if sid in sources or not ready[case["id"]]:
|
||||
continue
|
||||
snapshot = snapshots[sid]
|
||||
recipe = load_repo_recipe(args.manifest.parent / snapshot["recipe"])
|
||||
recipe = replace(recipe, commit=snapshot.get("commit", recipe.commit))
|
||||
source = checkout_path(recipe, args.cache)
|
||||
objects = git(source, "rev-parse", "--path-format=absolute", "--git-path", "objects").decode().strip()
|
||||
sources[sid] = (recipe.commit, objects)
|
||||
(bare / "objects/info/alternates").write_text("\n".join(sorted({value[1] for value in sources.values()})) + "\n")
|
||||
pins = {sid: pin_snapshot(bare, commit, snapshots[sid]) for sid, (commit, _) in sources.items()}
|
||||
write(
|
||||
args.out / "replay-pins.json",
|
||||
{"snapshots": pins, "provenance": "Full upstream trees; declared authored edits in a private bare Git repository."},
|
||||
)
|
||||
for case in cases:
|
||||
if not ready[case["id"]]:
|
||||
evidence["cases"].append({"case_id": case["id"], "status": "missing_input_prerequisite"})
|
||||
continue
|
||||
current = _provenance(args.manifest.parent, settings)
|
||||
if any(current[key] != initial[key] for key in keys):
|
||||
raise ValueError("core/facts/query/policy changed after freezing")
|
||||
if case["id"].startswith("nodegoat-") and evidence["freeze_after_pmpro"] is None:
|
||||
evidence["freeze_after_pmpro"] = {key: current[key] for key in keys}
|
||||
write(args.out / "freeze-after-pmpro.json", evidence["freeze_after_pmpro"])
|
||||
artifact_path = args.out / (case["id"] + ".json")
|
||||
command = [
|
||||
"ousast",
|
||||
"pre-push",
|
||||
str(bare),
|
||||
"--base",
|
||||
pins[case["base"]],
|
||||
"--head",
|
||||
pins[case["tip"]],
|
||||
"--artifact",
|
||||
str(artifact_path),
|
||||
"--deadline",
|
||||
"30",
|
||||
"--cancellation-allowance",
|
||||
"2",
|
||||
"--max-regions",
|
||||
"500",
|
||||
]
|
||||
start = time.monotonic()
|
||||
process = subprocess.run(command, capture_output=True, text=True, timeout=62)
|
||||
elapsed = time.monotonic() - start
|
||||
(args.out / (case["id"] + ".stdout")).write_text(process.stdout)
|
||||
(args.out / (case["id"] + ".stderr")).write_text(process.stderr)
|
||||
entry = {
|
||||
"case_id": case["id"],
|
||||
"command": command,
|
||||
"process_exit_code": process.returncode,
|
||||
"elapsed_seconds": elapsed,
|
||||
"artifact": artifact_path.name if artifact_path.exists() else None,
|
||||
"terminal": process.stdout,
|
||||
"core_unchanged": True,
|
||||
"input_pins": {"base": pins[case["base"]], "head": pins[case["tip"]]},
|
||||
}
|
||||
if artifact_path.exists():
|
||||
artifact = json.loads(artifact_path.read_text())
|
||||
# Unmeasured provenance caused by preparation timeout is explicit;
|
||||
# never replace it with a fictitious runtime measurement.
|
||||
entry["runtime_provenance_available"] = all(artifact["provenance"].get(key) == initial[key] for key in keys)
|
||||
record = execution_record(case["id"], artifact, elapsed=elapsed, target=config["targets"][case["id"]])
|
||||
entry["coverage_reasons"] = artifact["admission"]["coverage_reasons"]
|
||||
entry["stage_timings"] = artifact["timings"]
|
||||
entry["snapshot_bytes_read"] = sum(s["bytes_read"] for s in artifact["snapshots"])
|
||||
entry["classification"] = "scope/deadline" if record["coverage"] == "timeout" else "recorded context/capability limits"
|
||||
else:
|
||||
record = {
|
||||
"case_id": case["id"],
|
||||
"ranking_mode": "evidence",
|
||||
"rank": None,
|
||||
"selected": [],
|
||||
"deferred": [],
|
||||
"queries": [],
|
||||
"alerts": [],
|
||||
"delta": "unknown",
|
||||
"coverage": "error",
|
||||
"cache_state": "cold",
|
||||
"timings": {"total": elapsed, "build": 0, "query": 0},
|
||||
}
|
||||
entry["classification"] = "instrument/artifact unavailable; zero stage costs mean unmeasured"
|
||||
if process.returncode != 0:
|
||||
entry["classification"] = "instrument/CLI failure"
|
||||
record["coverage"] = "error"
|
||||
run["records"].append(record)
|
||||
evidence["cases"].append(entry)
|
||||
write(args.out / "run.json", run)
|
||||
write(args.out / "execution-evidence.json", evidence)
|
||||
print(json.dumps({"case": case["id"], "coverage": record["coverage"], "elapsed_seconds": elapsed}), flush=True)
|
||||
scored = score(profile, validation, run)
|
||||
write(args.out / "scorecard.json", scored)
|
||||
decision = {
|
||||
"decision": "NO-GO",
|
||||
"capabilities_enabled": False,
|
||||
"scorecard": "scorecard.json",
|
||||
"reason": "First cold diagnostic experiment; independent eligibility and joint quality/warm-latency gates remain unmet.",
|
||||
"recorded_cases": len(run["records"]),
|
||||
"declared_population": len(manifest["cases"]),
|
||||
"core_unchanged": True,
|
||||
"adapter_changes": [],
|
||||
"limits": [
|
||||
"No warm-cache measurements",
|
||||
"Untouched Ghost population not scanned",
|
||||
"PHP/Python admission populations missing",
|
||||
"libpng retained as unsupported envelope; no C/C++ detection claim",
|
||||
"Zero normal alerts from an empty registry cannot establish precision or useful recall",
|
||||
],
|
||||
}
|
||||
write(args.out / "decision.json", decision)
|
||||
print(json.dumps(decision), flush=True)
|
||||
return 1 # Measurement completed; the feasibility/admission decision is explicitly NO-GO.
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
spec = importlib.util.spec_from_file_location("push_measure", Path("benchmarks/push/measure.py"))
|
||||
measure = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(measure)
|
||||
|
||||
|
||||
def test_authored_snapshot_keeps_whole_tree_and_original_repository(tmp_path):
|
||||
from test_push_runner import history
|
||||
|
||||
root, base, head, git = history(tmp_path)
|
||||
(root / "unchanged.txt").write_text("first-party context remains in the whole tree")
|
||||
git("add", "unchanged.txt")
|
||||
git("commit", "-m", "unchanged context")
|
||||
head = git("rev-parse", "HEAD")
|
||||
# The helper consumes objects, never the deliberately dirty live source.
|
||||
before = git("show", head + ":api.js") + "\n"
|
||||
after = before.replace("req.body.value", '"fixed"')
|
||||
entry = {
|
||||
"id": "authored",
|
||||
"files": [
|
||||
{
|
||||
"path": "api.js",
|
||||
"size": len(after.encode()),
|
||||
"sha256": hashlib.sha256(after.encode()).hexdigest(),
|
||||
"original_sha256": hashlib.sha256(before.encode()).hexdigest(),
|
||||
"edit": {"before": "req.body.value", "after": '"fixed"', "provenance": "authored repair"},
|
||||
}
|
||||
],
|
||||
}
|
||||
bare = tmp_path / "objects.git"
|
||||
subprocess.run(["git", "init", "--bare", str(bare)], check=True, capture_output=True)
|
||||
(bare / "objects/info/alternates").write_text(str(root / ".git/objects") + "\n")
|
||||
original = (root / ".git/index").read_bytes(), (root / "api.js").read_bytes()
|
||||
oid = measure.pin_snapshot(bare, head, entry)
|
||||
result = subprocess.run(["git", "-C", str(bare), "show", oid + ":api.js"], check=True, capture_output=True)
|
||||
assert result.stdout.decode() == after
|
||||
retained = subprocess.run(["git", "-C", str(bare), "show", oid + ":unchanged.txt"], check=True, capture_output=True)
|
||||
assert retained.stdout == b"first-party context remains in the whole tree"
|
||||
|
||||
assert original == ((root / ".git/index").read_bytes(), (root / "api.js").read_bytes())
|
||||
assert oid == measure.pin_snapshot(bare, head, entry), "authored object identities must be reproducible"
|
||||
entry["files"][0]["sha256"] = "0" * 64
|
||||
with pytest.raises(ValueError, match="hash|identity"):
|
||||
measure.pin_snapshot(bare, head, entry)
|
||||
|
||||
|
||||
def payload():
|
||||
return {
|
||||
"result": {"coverage_status": "incomplete"},
|
||||
"admission": {"coverage_reasons": ["deadline_exhausted"], "dispositions": [], "defects": []},
|
||||
"scans": [],
|
||||
"timings": {"total_seconds": 30},
|
||||
}
|
||||
|
||||
|
||||
def test_timeout_stays_unresolved_and_uses_whole_cli_elapsed_time():
|
||||
record = measure.execution_record("case", payload(), elapsed=30.8)
|
||||
assert record["coverage"] == "timeout" and record["delta"] == "unknown"
|
||||
assert record["rank"] is None and record["queries"] == [] and record["alerts"] == []
|
||||
assert record["timings"]["total"] == 30.8 and record["cache_state"] == "cold"
|
||||
|
||||
|
||||
def test_zero_cli_exit_cannot_earn_negative_without_witness():
|
||||
data = payload()
|
||||
data["result"]["coverage_status"] = "complete_within_scope"
|
||||
data["admission"]["coverage_reasons"] = []
|
||||
identity = {"unit": "repository", "language": "php", "path": "api.php", "function": "handler", "family": "injection"}
|
||||
data["scans"] = [
|
||||
{
|
||||
"side": "head",
|
||||
"scan": {
|
||||
"scope": {"ranking_mode": "evidence", "selected": [{"identity": identity}], "deferred": []},
|
||||
"question_outcomes": [{"identity": identity, "status": "completed", "raw_rows_json": "[]"}],
|
||||
"findings": [],
|
||||
"build_seconds": 1,
|
||||
"query_seconds": 2,
|
||||
},
|
||||
}
|
||||
]
|
||||
record = measure.execution_record("case", data, elapsed=4)
|
||||
assert record["queries"][0]["outcome"] == "unknown"
|
||||
assert "witness" not in record["queries"][0]
|
||||
assert record["alerts"] == []
|
||||
Reference in New Issue
Block a user