mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(pre-push-safety-net): measure frozen runtime feasibility and retain no-go
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
# Runtime follow-up from pre-push-safety-net task 8.2
|
||||||
|
|
||||||
|
Status: frozen measurement complete, runtime NO-GO; investigation routed to the existing engine/runtime
|
||||||
|
owner. This note does not change contributor-scan task approvals or detector semantics.
|
||||||
|
Final scorecard and exact instrument identities are linked below.
|
||||||
|
|
||||||
|
The NodeGoat production transaction uses the existing evidence ranker and full tracked
|
||||||
|
first-party graph. Initial packaged process receipts show approximately 5 seconds for
|
||||||
|
the JavaScript frontend, 14 seconds for overlays and 12 seconds for census before the
|
||||||
|
first query. Each query invokes another JVM. Both comparison sides retain independent
|
||||||
|
graph/answer identities. These are observed single-stage costs, not p95 estimates.
|
||||||
|
|
||||||
|
The existing engine boundary owns frontend execution, overlays, graph census, queries,
|
||||||
|
process cancellation and validated graph leases. Investigate batching census and query
|
||||||
|
work into an engine session, amortizing JVM startup, and reusing complete immutable
|
||||||
|
base-side artifacts. Measure the additional costs of graph parsing, overlay work and
|
||||||
|
loading separately. A long-lived local engine would need request deadlines, isolated
|
||||||
|
leases, reliable cancellation/recovery and resource limits before it can replace the
|
||||||
|
current disposable subprocess lifecycle.
|
||||||
|
|
||||||
|
Any proposed change must preserve named source/census receipts, actual selected/deferred
|
||||||
|
question identities, first-party-only graph contents, exact base/head comparisons and
|
||||||
|
fresh-answer census validation. Compare evidence against cold execution. Keep the
|
||||||
|
30-second warm p95 and 2-second cancellation/report gates unchanged. Differential graph
|
||||||
|
mutation remains a separate experiment requiring cold-equivalence evidence. Ranker
|
||||||
|
retuning or shrinking the evaluation population cannot repair fixed engine startup cost.
|
||||||
|
|
||||||
|
The initial long-budget NodeGoat priming transaction finished in 224.47 seconds with
|
||||||
|
incomplete coverage. Its head partition records 44 input paths and 95,411 source bytes,
|
||||||
|
but the scan reports `cpg_empty` and `partition_file_census_incomplete`; the declared
|
||||||
|
contributions handler remains an unresolved file-level question. No qualified graph
|
||||||
|
or answer hit is established for the following identical comparison. This is a named
|
||||||
|
instrument/coverage gap, not evidence that the target is safe or that the ranker missed
|
||||||
|
a proven candidate. Reconcile raw frontend/census filenames with the 44 input paths,
|
||||||
|
including the explicit test-retention policy, before attributing this to cache identity
|
||||||
|
or detector semantics. Keep this investigation separate from JVM startup optimization.
|
||||||
|
|
||||||
|
Final task 8.2 receipts: `benchmarks/measurements/2026-09-13-nodegoat-feasibility.json`
|
||||||
|
and its compressed full evidence bundle. All 21 deadline-bound transactions timed out,
|
||||||
|
0/24 declared target checks completed, observed workload p95 30.631–30.912 seconds.
|
||||||
|
Every measured artifact matches the frozen installed semantics. No source, engine,
|
||||||
|
query, fact or ranking change was made during this run. The follow-up remains necessary
|
||||||
|
before rollout; the evaluation task does not authorize weakening its gates.
|
||||||
@@ -35,3 +35,74 @@ missing-base, hook-exit and ordinary-scan regressions.
|
|||||||
- CLAIM: Task 8.1 integration regression coverage delivered.
|
- CLAIM: Task 8.1 integration regression coverage delivered.
|
||||||
- EVIDENCE: `/tmp/group8-1-focused.log`: 3 passed; `/tmp/group8-1-full.log`: 1299 passed, 9 skipped; static checks exit 0.
|
- EVIDENCE: `/tmp/group8-1-focused.log`: 3 passed; `/tmp/group8-1-full.log`: 1299 passed, 9 skipped; static checks exit 0.
|
||||||
- GAPS: These controlled failures do not qualify real detector capabilities or representative latency.
|
- GAPS: These controlled failures do not qualify real detector capabilities or representative latency.
|
||||||
|
|
||||||
|
## Task 8.2 brief
|
||||||
|
|
||||||
|
Requirements 4.1, 4.2, 6.2, 6.3, 6.5 and 7.3; evaluation/runtime integration boundary.
|
||||||
|
Freeze full pinned NodeGoat histories before measuring: three distinct function edits,
|
||||||
|
dependency edits, configuration edits, growth histories and multi-ref transactions,
|
||||||
|
plus three identical-comparison and three uncached controls. Each edit repetition has
|
||||||
|
a distinct tree, avoiding accidental identical-tip reuse. Retain all 21 transactions,
|
||||||
|
exact source byte receipt, installed semantics, hardware, selected/deferred counts,
|
||||||
|
expected target checks, stage process receipts and whole CLI elapsed time. A separate
|
||||||
|
600-second priming run is diagnostic and cannot replace a 30-second gate sample.
|
||||||
|
|
||||||
|
Nearest-rank p50/p95 include timeout durations and report small sample counts explicitly.
|
||||||
|
A missing artifact or abnormal CLI exit is an instrument error, never a clean result.
|
||||||
|
Source/vendor/context/coverage gaps remain gaps. NodeGoat remains development data;
|
||||||
|
this runtime experiment cannot qualify independent alert precision or broad production
|
||||||
|
performance. Engine lifecycle costs route to contributor-scan's runtime follow-up.
|
||||||
|
|
||||||
|
### Task 8.2 measured outcome
|
||||||
|
|
||||||
|
Packaged image `af5b896b406fe98acb3d00bb2180a7549febd6dd285f0b63087538118e52e1f4`,
|
||||||
|
network disabled, UID 1000, 3 GiB memory limit; CPU/affinity and installed identities
|
||||||
|
are in `benchmarks/measurements/2026-09-13-nodegoat-feasibility.json`. All 21 artifacts
|
||||||
|
match the frozen core/facts/queries/policy/runtime/semantics. All 24 declared target
|
||||||
|
checks remain incomplete. Priming took 224.47 seconds and did not qualify graph/answer
|
||||||
|
reuse; subsequent cache attempts establish discovery reuse only.
|
||||||
|
|
||||||
|
| Workload | n | p50 seconds | p95 seconds | Timeouts | Completed target checks |
|
||||||
|
|---|---:|---:|---:|---:|---:|
|
||||||
|
| Identical comparison | 3 | 30.635 | 30.644 | 3 | 0/3 |
|
||||||
|
| Distinct function edits | 3 | 30.611 | 30.663 | 3 | 0/3 |
|
||||||
|
| Dependency edits | 3 | 30.627 | 30.712 | 3 | 0/3 |
|
||||||
|
| Configuration edits | 3 | 30.606 | 30.728 | 3 | 0/3 |
|
||||||
|
| Cold | 3 | 30.609 | 30.631 | 3 | 0/3 |
|
||||||
|
| Growth | 3 | 30.894 | 30.912 | 3 | 0/3 |
|
||||||
|
| Multi-ref | 3 | 30.652 | 30.692 | 3 | 0/6 |
|
||||||
|
|
||||||
|
These are observed timings of the declared attempts, not proof of a qualified warm
|
||||||
|
graph path. All timeouts remain in the population. Cancellation/reporting meets the
|
||||||
|
2-second allowance in these samples; latency and completion do not meet the joint gate.
|
||||||
|
Runtime and rollout verdict: **NO-GO**. The n=3 quantiles do not establish multi-project
|
||||||
|
production performance. The full 116-file receipt bundle is 1,254,241 bytes compressed,
|
||||||
|
SHA-256 `3174bfc0bd39e10b685d06d8bc28ab39450368f1a441e64bbb5a27a256bf13c6`.
|
||||||
|
Decompression was compared to every original file. Both measured instrument bytes and
|
||||||
|
the original frozen scorecard are retained; later scorer hardening preserves the
|
||||||
|
measurements and adds missing-workload and multi-ref-denominator checks.
|
||||||
|
|
||||||
|
Owner routing: `../contributor-scan/pre-push-runtime-followup.md` separates raw named
|
||||||
|
census reconciliation from frontend/overlay/JVM/query lifecycle optimization. Do not
|
||||||
|
retune the ranker or weaken the population/gates to compensate.
|
||||||
|
|
||||||
|
## Status Report
|
||||||
|
- STATUS: READY_FOR_REVIEW
|
||||||
|
- TASK: 8.2
|
||||||
|
- RED_PHASE_OUTPUT: `/tmp/group8-2-red.log` (missing scorer) and `/tmp/group8-2-flag-off.log` (disabled scorer). ON passed; switch removed, seven final scorer tests passed.
|
||||||
|
- TESTS_RUN: full suite 1306 passed, 9 skipped in 117.63s; ruff, formatting, mypy and diff checks pass. Frozen packaged measurement completed with exit 1 and full NO-GO scorecard, no missing artifacts or abnormal CLI exits.
|
||||||
|
|
||||||
|
## Review Verdict
|
||||||
|
- VERDICT: APPROVED
|
||||||
|
- TASK: 8.2
|
||||||
|
- MECHANICAL_RESULTS: actual harness, fixtures, scorecard and receipt bundle reviewed. Input bytes/pins, distinct edit histories, all 21 runs, stage exits, fixed target denominators and source/runtime identity checks retained. Invalid numbers, duplicate/unplanned rows, missing workload classes and missing cases cannot qualify runtime. No production/ranker/fact changes.
|
||||||
|
- FINDINGS: Initial warm-graph precondition failed; the report explicitly limits reuse claims. The unchanged runtime gates fail. Engine lifecycle and named census follow-up routed to contributor-scan.
|
||||||
|
- RED phase: VERIFIED.
|
||||||
|
- SUMMARY: Reproducible runtime evaluation is complete with a measured NO-GO result.
|
||||||
|
|
||||||
|
## Verification Result
|
||||||
|
- STATUS: VERIFIED
|
||||||
|
- CLAIM_TYPE: TASK
|
||||||
|
- CLAIM: Task 8.2 completed its prescribed measurement and owner routing; rollout remains NO-GO.
|
||||||
|
- EVIDENCE: Versioned scorecard and 116-file verified bundle; seven scorer controls and full regression suite pass.
|
||||||
|
- GAPS: No qualified warm graph path or independent capability eligibility established. These failed gates cannot authorize rollout.
|
||||||
|
|||||||
@@ -116,9 +116,10 @@
|
|||||||
"7.1",
|
"7.1",
|
||||||
"7.2",
|
"7.2",
|
||||||
"7.3",
|
"7.3",
|
||||||
"8.1"
|
"8.1",
|
||||||
|
"8.2"
|
||||||
],
|
],
|
||||||
"remaining_tasks": 3,
|
"remaining_tasks": 2,
|
||||||
"verification_record": "implementation-group-8.md",
|
"verification_record": "implementation-group-8.md",
|
||||||
"verification_records": [
|
"verification_records": [
|
||||||
"implementation-group-1.md",
|
"implementation-group-1.md",
|
||||||
@@ -132,6 +133,6 @@
|
|||||||
],
|
],
|
||||||
"next_group": 8,
|
"next_group": 8,
|
||||||
"blocked_tasks": [],
|
"blocked_tasks": [],
|
||||||
"feasibility": "NO-GO for rollout: compatible identical-revision reuse verified; representative changed-code p95/completion and independent alert eligibility remain unmet."
|
"feasibility": "NO-GO: all 21 frozen NodeGoat transactions timed out; 0/24 declared target checks completed. Cancellation/reporting remained below 2 seconds. Priming did not qualify graph reuse; named census and engine lifecycle work routed to contributor-scan."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ independent capability qualification. Groups 7–8 remain pending; rollout remai
|
|||||||
- Done when the integration suite covers the critical flows through the real CLI/backend seams and preserves ordinary scan behavior.
|
- Done when the integration suite covers the critical flows through the real CLI/backend seams and preserves ordinary scan behavior.
|
||||||
- _Boundary: Push runner integration tests_
|
- _Boundary: Push runner integration tests_
|
||||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.2, 2.3, 4.2, 4.4, 5.2, 5.5, 7.1, 7.4, 8.2_
|
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.2, 2.3, 4.2, 4.4, 5.2, 5.5, 7.1, 7.4, 8.2_
|
||||||
- [ ] 8.2 Measure realistic cache and deadline feasibility
|
- [x] 8.2 Measure realistic cache and deadline feasibility
|
||||||
- Measure identical-tip reuse, a warm one-function edit, dependency/configuration edits, cold starts, repository growth and multi-ref workloads on declared hardware.
|
- Measure identical-tip reuse, a warm one-function edit, dependency/configuration edits, cold starts, repository growth and multi-ref workloads on declared hardware.
|
||||||
- If reuse correctness passes but changed-code checks time out, identify preparation, frontend/overlay/census and query costs separately and route the remaining optimization to its owning boundary before rollout GO. Preserve the declared gates and do not substitute identical-tip latency.
|
- If reuse correctness passes but changed-code checks time out, identify preparation, frontend/overlay/census and query costs separately and route the remaining optimization to its owning boundary before rollout GO. Preserve the declared gates and do not substitute identical-tip latency.
|
||||||
- Publish p50/p95, timeouts, cancellation allowance, completed/deferred population and stage costs; the denominator is fixed before ranker selection.
|
- Publish p50/p95, timeouts, cancellation allowance, completed/deferred population and stage costs; the denominator is fixed before ranker selection.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -158,3 +158,33 @@ The output directory must be empty and writable by the image's nonroot user. Col
|
|||||||
30-second deadline and 2-second cancellation allowance. Default normal capabilities remain
|
30-second deadline and 2-second cancellation allowance. Default normal capabilities remain
|
||||||
empty. A passing reuse control cannot authorize rollout or turn unresolved coverage into clean
|
empty. A passing reuse control cannot authorize rollout or turn unresolved coverage into clean
|
||||||
analysis; groups 7–8 still own the real interface and joint evaluation.
|
analysis; groups 7–8 still own the real interface and joint evaluation.
|
||||||
|
|
||||||
|
### Frozen runtime feasibility (task 8.2)
|
||||||
|
|
||||||
|
`feasibility.py` prepares a full pinned NodeGoat history in a private bare repository,
|
||||||
|
verifies the declared source/license blobs, and freezes 21 transactions before any
|
||||||
|
scanner invocation: three each of identical-tip, function edit, dependency edit,
|
||||||
|
configuration edit, cold, 100-module growth and multi-ref workloads. Each changed-code
|
||||||
|
repetition has distinct content. It invokes the production CLI through `trace_cli.py`,
|
||||||
|
which records engine-process durations and exit receipts without changing analysis.
|
||||||
|
Frontend, overlay, census and query costs remain separate. A 600-second priming attempt
|
||||||
|
is outside the gate population; each measured transaction retains the 30+2-second
|
||||||
|
budget. An incomplete priming run does not establish warm graph reuse.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm --network none --memory 3g --entrypoint python \
|
||||||
|
-v "$PWD:/context:ro" \
|
||||||
|
-v "$HOME/.cache/openultrasast/repos:/cache:ro" \
|
||||||
|
-v /absolute/writable/results:/results \
|
||||||
|
openultrasast:dev /context/benchmarks/push/feasibility.py \
|
||||||
|
--manifest /context/benchmarks/push/inputs.json --cache /cache --out /results/new-feasibility
|
||||||
|
```
|
||||||
|
|
||||||
|
The output directory must not exist. `profile.json` records the planned commits,
|
||||||
|
source byte receipt, installed semantics, CPU information/affinity and container memory
|
||||||
|
limit. Every run retains the full artifact, stdout/stderr, process-stage receipts and
|
||||||
|
measurement. `scorecard.json` retains missing runs and timeouts in the declared population,
|
||||||
|
reports nearest-rank p50/p95 with sample counts, and never treats no answers as complete.
|
||||||
|
Exit 1 with a complete scorecard is measured NO-GO; a missing scorecard is an instrument
|
||||||
|
failure. Three samples per workload on development NodeGoat cannot independently qualify
|
||||||
|
a production language/framework/family or establish broad multi-project performance.
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
"""Frozen repeated NodeGoat runtime experiment; never a capability qualification."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def summarize(plan, runs):
|
||||||
|
if not plan:
|
||||||
|
raise ValueError("empty measurement population")
|
||||||
|
ids = {row["id"] for row in plan}
|
||||||
|
observed = {row["id"]: row for row in runs}
|
||||||
|
if len(ids) != len(plan) or len(observed) != len(runs) or not observed.keys() <= ids:
|
||||||
|
raise ValueError("duplicate or unplanned measurement")
|
||||||
|
for row in runs:
|
||||||
|
elapsed = row.get("elapsed_seconds")
|
||||||
|
if elapsed is not None and (type(elapsed) not in (int, float) or not math.isfinite(elapsed) or elapsed < 0):
|
||||||
|
raise ValueError("invalid elapsed measurement")
|
||||||
|
result = {
|
||||||
|
"workloads": {},
|
||||||
|
"runtime_verdict": "PASS",
|
||||||
|
"rollout": "NO-GO",
|
||||||
|
"qualification_limit": "Development NodeGoat, n=3 per workload; independent quality and multi-project performance not established.",
|
||||||
|
}
|
||||||
|
for name in dict.fromkeys(row["workload"] for row in plan):
|
||||||
|
population = [p for p in plan if p["workload"] == name]
|
||||||
|
records = [observed[p["id"]] for p in population if p["id"] in observed]
|
||||||
|
samples = sorted(r["elapsed_seconds"] for r in records if r.get("elapsed_seconds") is not None)
|
||||||
|
complete = sum(bool(r.get("target_completed")) for r in records)
|
||||||
|
target_count = sum(r.get("completed_targets", int(bool(r.get("target_completed")))) for r in records)
|
||||||
|
expected_count = sum(p.get("expected_targets", 1) for p in population)
|
||||||
|
covered = sum(r.get("coverage") == "complete_within_scope" and not r.get("deferred") for r in records)
|
||||||
|
row = dict(
|
||||||
|
population=len(population),
|
||||||
|
observed=len(records),
|
||||||
|
missing=len(population) - len(records),
|
||||||
|
latency_samples=len(samples),
|
||||||
|
p50_seconds=samples[math.ceil(0.5 * len(samples)) - 1] if samples else None,
|
||||||
|
p95_seconds=samples[math.ceil(0.95 * len(samples)) - 1] if samples else None,
|
||||||
|
timeouts=sum(bool(r.get("timed_out")) for r in records),
|
||||||
|
completed_targets=target_count,
|
||||||
|
expected_targets=expected_count,
|
||||||
|
complete_target_cases=complete,
|
||||||
|
completion=complete / len(population),
|
||||||
|
complete_coverage_cases=covered,
|
||||||
|
cancellation_overrun_seconds=max(
|
||||||
|
[max(0, r["elapsed_seconds"] - 30) for r in records if r.get("timed_out") and r.get("elapsed_seconds") is not None] or [0]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
row["pass"] = (
|
||||||
|
not row["missing"]
|
||||||
|
and len(samples) == len(population)
|
||||||
|
and complete / len(population) >= 0.95
|
||||||
|
and covered / len(population) >= 0.95
|
||||||
|
and max(samples, default=33) <= 32
|
||||||
|
and (name == "cold" or row["p95_seconds"] <= 30)
|
||||||
|
and row["cancellation_overrun_seconds"] <= 2
|
||||||
|
and not any(r.get("instrument_error") for r in records)
|
||||||
|
)
|
||||||
|
if not row["pass"]:
|
||||||
|
result["runtime_verdict"] = "NO-GO"
|
||||||
|
result["workloads"][name] = row
|
||||||
|
required = {"identical-tip", "function-edit", "dependency-edit", "configuration-edit", "cold", "growth", "multi-ref"}
|
||||||
|
result["missing_workloads"] = sorted(required - result["workloads"].keys())
|
||||||
|
if result["missing_workloads"]:
|
||||||
|
result["runtime_verdict"] = "NO-GO"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def write(path, data):
|
||||||
|
path.write_text(json.dumps(data, indent=2, allow_nan=False) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
from measure import git, pin_snapshot
|
||||||
|
|
||||||
|
from openultrasast.model.scan import ScanBudget
|
||||||
|
from openultrasast.push.runner import _provenance
|
||||||
|
from openultrasast.repos import checkout_path, load_repo_recipe
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--out", type=Path, required=True)
|
||||||
|
parser.add_argument("--cache", type=Path, required=True)
|
||||||
|
parser.add_argument("--manifest", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.out.mkdir(parents=True, exist_ok=False)
|
||||||
|
out = args.out.resolve()
|
||||||
|
manifest = json.loads(args.manifest.read_bytes())
|
||||||
|
snapshots = {s["id"]: s for s in manifest["snapshots"]}
|
||||||
|
selected = [s for s in snapshots if s in ("nodegoat-vulnerable", "nodegoat-fixed")]
|
||||||
|
if len(selected) != 2:
|
||||||
|
raise ValueError("declared NodeGoat pair unavailable")
|
||||||
|
recipe = load_repo_recipe(args.manifest.parent / snapshots[selected[0]]["recipe"])
|
||||||
|
source = checkout_path(recipe, args.cache)
|
||||||
|
bare = out / "objects.git"
|
||||||
|
bare.mkdir()
|
||||||
|
git(bare, "init", "--bare")
|
||||||
|
objects = git(source, "rev-parse", "--path-format=absolute", "--git-path", "objects").decode().strip()
|
||||||
|
(bare / "objects/info/alternates").write_text(objects + "\n")
|
||||||
|
pins = {sid: pin_snapshot(bare, recipe.commit, snapshots[sid]) for sid in selected}
|
||||||
|
head, base = pins["nodegoat-vulnerable"], pins["nodegoat-fixed"]
|
||||||
|
target_path = "app/routes/contributions.js"
|
||||||
|
original = git(bare, "cat-file", "blob", head + ":" + target_path)
|
||||||
|
assert len(original) > 0
|
||||||
|
|
||||||
|
def mutate(changes, title):
|
||||||
|
git(bare, "read-tree", head)
|
||||||
|
for path, data in changes.items():
|
||||||
|
blob = git(bare, "hash-object", "-w", "--stdin", data=data).decode().strip()
|
||||||
|
git(bare, "update-index", "--add", "--cacheinfo", "100644", blob, path)
|
||||||
|
tree = git(bare, "write-tree").decode().strip()
|
||||||
|
return git(bare, "commit-tree", tree, "-p", head, data=(title + "\n").encode()).decode().strip()
|
||||||
|
|
||||||
|
package = json.loads(git(bare, "cat-file", "blob", head + ":package.json"))
|
||||||
|
configuration = git(bare, "cat-file", "blob", head + ":config/env/all.js")
|
||||||
|
assert b"4000" in configuration and original.count(b"eval(req.body.preTax)") == 1
|
||||||
|
plan = []
|
||||||
|
for repeat in range(1, 4):
|
||||||
|
edited = mutate(
|
||||||
|
{target_path: original.replace(b"eval(req.body.preTax)", f"Number(req.body.preTax) + {repeat}".encode(), 1)},
|
||||||
|
f"function edit {repeat}",
|
||||||
|
)
|
||||||
|
dependency = dict(package)
|
||||||
|
dependency["dependencies"] = dict(package["dependencies"])
|
||||||
|
dependency["dependencies"]["express"] = f"4.22.{repeat}"
|
||||||
|
dep = mutate({"package.json": (json.dumps(dependency, indent=2) + "\n").encode()}, f"dependency edit {repeat}")
|
||||||
|
config = mutate({"config/env/all.js": configuration.replace(b"4000", str(4100 + repeat).encode(), 1)}, f"config edit {repeat}")
|
||||||
|
growth = mutate(
|
||||||
|
{
|
||||||
|
f"app/routes/runtime-growth-{i}.js": (
|
||||||
|
f"module.exports = function route{i}(req, res) {{ return res.json({{value: req.body.value, revision: {repeat}}}); }};\n"
|
||||||
|
).encode()
|
||||||
|
for i in range(100)
|
||||||
|
},
|
||||||
|
f"growth {repeat}",
|
||||||
|
)
|
||||||
|
for workload, comparisons in [
|
||||||
|
("identical-tip", [(base, head)]),
|
||||||
|
("function-edit", [(head, edited)]),
|
||||||
|
("dependency-edit", [(head, dep)]),
|
||||||
|
("configuration-edit", [(head, config)]),
|
||||||
|
("cold", [(base, head)]),
|
||||||
|
("growth", [(head, growth)]),
|
||||||
|
("multi-ref", [(head, edited), (head, config)]),
|
||||||
|
]:
|
||||||
|
plan.append(dict(id=f"{workload}-{repeat}", workload=workload, comparisons=comparisons, expected_targets=len(comparisons)))
|
||||||
|
installed = _provenance(bare, ScanBudget(max_model_calls=0, max_regions=500, order_by_evidence=True))
|
||||||
|
profile = dict(
|
||||||
|
schema_version=1,
|
||||||
|
plan=plan,
|
||||||
|
source_commit=recipe.commit,
|
||||||
|
pins=pins,
|
||||||
|
input_probe=dict(path=target_path, bytes=len(original), sha256=hashlib.sha256(original).hexdigest()),
|
||||||
|
provenance=installed,
|
||||||
|
hardware=dict(
|
||||||
|
platform=platform.platform(),
|
||||||
|
cpu_affinity=sorted(os.sched_getaffinity(0)),
|
||||||
|
cpuinfo=Path("/proc/cpuinfo").read_text(),
|
||||||
|
memory_limit=Path("/sys/fs/cgroup/memory.max").read_text().strip(),
|
||||||
|
),
|
||||||
|
gates=dict(deadline_seconds=30, cancellation_seconds=2, warm_p95_seconds=30, completion=0.95),
|
||||||
|
lab_priming_deadline=600,
|
||||||
|
quantile="nearest-rank; includes timeout elapsed values; missing samples explicit",
|
||||||
|
target=dict(path=target_path, function="handleContributionsUpdate", family="injection"),
|
||||||
|
harness_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||||
|
instrumentation_sha256=hashlib.sha256(Path(__file__).with_name("trace_cli.py").read_bytes()).hexdigest(),
|
||||||
|
)
|
||||||
|
write(out / "profile.json", profile)
|
||||||
|
runs = []
|
||||||
|
|
||||||
|
def run(case, deadline, cache=True):
|
||||||
|
identity = case["id"]
|
||||||
|
artifact = out / (identity + ".json")
|
||||||
|
trace = out / (identity + ".trace.jsonl")
|
||||||
|
lines = "".join(f"refs/heads/local{i} {tip} refs/heads/result{i} {old}\n" for i, (old, tip) in enumerate(case["comparisons"]))
|
||||||
|
command = [
|
||||||
|
sys.executable,
|
||||||
|
str(Path(__file__).with_name("trace_cli.py")),
|
||||||
|
"pre-push",
|
||||||
|
str(bare),
|
||||||
|
"--remote",
|
||||||
|
"evaluation",
|
||||||
|
"/local-only",
|
||||||
|
"--artifact",
|
||||||
|
str(artifact),
|
||||||
|
"--deadline",
|
||||||
|
str(deadline),
|
||||||
|
]
|
||||||
|
if cache:
|
||||||
|
command += ["--cache-dir", str(out / "artifacts")]
|
||||||
|
start = time.monotonic()
|
||||||
|
process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
start_new_session=True,
|
||||||
|
env={**os.environ, "OUSAST_EVALUATION_TRACE": str(trace)},
|
||||||
|
)
|
||||||
|
error = None
|
||||||
|
try:
|
||||||
|
stdout, stderr = process.communicate(lines, timeout=deadline + 5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
os.killpg(process.pid, signal.SIGKILL)
|
||||||
|
stdout, stderr = process.communicate(timeout=2)
|
||||||
|
error = "outer_deadline_exceeded"
|
||||||
|
elapsed = time.monotonic() - start
|
||||||
|
(out / (identity + ".stdout")).write_text(stdout)
|
||||||
|
(out / (identity + ".stderr")).write_text(stderr)
|
||||||
|
data = json.loads(artifact.read_bytes()) if artifact.is_file() else None
|
||||||
|
if data is None:
|
||||||
|
error = error or "missing_artifact"
|
||||||
|
if process.returncode != 0:
|
||||||
|
error = error or "unexpected_cli_exit"
|
||||||
|
completed = 0
|
||||||
|
if data:
|
||||||
|
for scan in data["scans"]:
|
||||||
|
if scan["side"] == "head" and any(
|
||||||
|
q["status"] == "completed" and all(q["identity"][key] == value for key, value in profile["target"].items())
|
||||||
|
for q in scan["scan"]["question_outcomes"]
|
||||||
|
):
|
||||||
|
completed += 1
|
||||||
|
record = dict(
|
||||||
|
id=identity,
|
||||||
|
elapsed_seconds=elapsed,
|
||||||
|
exit_code=process.returncode,
|
||||||
|
instrument_error=error,
|
||||||
|
timed_out=bool(data and any("deadline" in reason for reason in data["admission"]["coverage_reasons"]))
|
||||||
|
or error == "outer_deadline_exceeded",
|
||||||
|
target_completed=completed == case["expected_targets"],
|
||||||
|
completed_targets=completed,
|
||||||
|
expected_targets=case["expected_targets"],
|
||||||
|
coverage=data["result"]["coverage_status"] if data else "unavailable",
|
||||||
|
timings=data["timings"] if data else {},
|
||||||
|
selected=sum(len(s["scan"]["scope"]["selected"]) for s in data["scans"] if s["side"] == "head" and s["scan"]["scope"])
|
||||||
|
if data
|
||||||
|
else 0,
|
||||||
|
deferred=sum(len(s["scan"]["scope"]["deferred"]) for s in data["scans"] if s["side"] == "head" and s["scan"]["scope"])
|
||||||
|
if data
|
||||||
|
else 0,
|
||||||
|
stages=[json.loads(line) for line in trace.read_text().splitlines()] if trace.exists() else [],
|
||||||
|
)
|
||||||
|
write(out / (identity + ".measurement.json"), record)
|
||||||
|
print(json.dumps({k: record[k] for k in ("id", "elapsed_seconds", "target_completed", "coverage", "instrument_error")}), flush=True)
|
||||||
|
return record
|
||||||
|
|
||||||
|
prime = run(dict(id="lab-prime", comparisons=[(base, head)], expected_targets=1), 600)
|
||||||
|
write(out / "priming.json", prime)
|
||||||
|
for case in plan:
|
||||||
|
runs.append(run(case, 30, cache=case["workload"] != "cold"))
|
||||||
|
write(out / "runs.json", runs)
|
||||||
|
write(out / "scorecard.json", summarize(plan, runs))
|
||||||
|
final = summarize(plan, runs)
|
||||||
|
final["profile_sha256"] = hashlib.sha256((out / "profile.json").read_bytes()).hexdigest()
|
||||||
|
final["priming_completed"] = prime["target_completed"]
|
||||||
|
write(out / "scorecard.json", final)
|
||||||
|
return 0 if final["runtime_verdict"] == "PASS" else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Transparent engine-process timing around the production CLI (evaluation only)."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from openultrasast.cli import main
|
||||||
|
from openultrasast.cpg.backend import JoernBackend
|
||||||
|
|
||||||
|
original = JoernBackend._run_bounded
|
||||||
|
trace = Path(os.environ["OUSAST_EVALUATION_TRACE"])
|
||||||
|
|
||||||
|
|
||||||
|
def measured(self, command, **kwargs):
|
||||||
|
executable = Path(command[0]).name
|
||||||
|
scripts = [Path(arg).name for arg in command if arg.endswith(".sc")]
|
||||||
|
stage = (
|
||||||
|
"overlay"
|
||||||
|
if "overlay.sc" in scripts
|
||||||
|
else "census"
|
||||||
|
if "census.sc" in scripts
|
||||||
|
else "query"
|
||||||
|
if scripts
|
||||||
|
else "frontend"
|
||||||
|
if "cpg" in executable or "joern-parse" in executable
|
||||||
|
else "input_probe"
|
||||||
|
)
|
||||||
|
started = time.monotonic()
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
result = original(self, command, **kwargs)
|
||||||
|
return result
|
||||||
|
finally:
|
||||||
|
with trace.open("a") as stream:
|
||||||
|
stream.write(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"stage": stage,
|
||||||
|
"seconds": time.monotonic() - started,
|
||||||
|
"exit_code": result.returncode if result is not None else None,
|
||||||
|
"receipt": result is not None,
|
||||||
|
"executable": executable,
|
||||||
|
"scripts": scripts,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
JoernBackend._run_bounded = measured
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location("feasibility", Path("benchmarks/push/feasibility.py"))
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
|
||||||
|
def test_timeouts_and_missing_runs_remain_in_fixed_population():
|
||||||
|
plan = [{"id": str(i), "workload": "function-edit"} for i in range(3)]
|
||||||
|
runs = [
|
||||||
|
{"id": "0", "elapsed_seconds": 2, "target_completed": True, "timed_out": False},
|
||||||
|
{"id": "1", "elapsed_seconds": 30.5, "target_completed": False, "timed_out": True},
|
||||||
|
]
|
||||||
|
result = module.summarize(plan, runs)
|
||||||
|
row = result["workloads"]["function-edit"]
|
||||||
|
assert row["population"] == 3 and row["missing"] == 1
|
||||||
|
assert row["p95_seconds"] == 30.5 and row["timeouts"] == 1
|
||||||
|
assert row["completed_targets"] == 1
|
||||||
|
assert result["runtime_verdict"] == "NO-GO"
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicate_or_unplanned_measurement_is_rejected():
|
||||||
|
plan = [{"id": "a", "workload": "cold"}]
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.summarize(plan, [{"id": "outside"}])
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.summarize(plan, [{"id": "a"}, {"id": "a"}])
|
||||||
|
|
||||||
|
|
||||||
|
def test_identical_tip_alone_cannot_qualify_runtime():
|
||||||
|
plan = [{"id": "a", "workload": "identical-tip"}]
|
||||||
|
runs = [{"id": "a", "elapsed_seconds": 1, "target_completed": True, "coverage": "complete_within_scope", "deferred": 0}]
|
||||||
|
assert module.summarize(plan, runs)["runtime_verdict"] == "NO-GO"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("elapsed", [-1, float("nan"), float("inf")])
|
||||||
|
def test_invalid_elapsed_is_an_instrument_error(elapsed):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.summarize([{"id": "a", "workload": "cold"}], [{"id": "a", "elapsed_seconds": elapsed}])
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_ref_target_denominator_is_fixed_before_execution():
|
||||||
|
result = module.summarize(
|
||||||
|
[{"id": "a", "workload": "multi-ref", "expected_targets": 2}],
|
||||||
|
[{"id": "a", "elapsed_seconds": 30.5, "target_completed": False, "completed_targets": 1, "timed_out": True}],
|
||||||
|
)
|
||||||
|
assert result["workloads"]["multi-ref"]["expected_targets"] == 2
|
||||||
|
assert result["workloads"]["multi-ref"]["completed_targets"] == 1
|
||||||
Reference in New Issue
Block a user