26 KiB
Here’s a crisp, no-drama standup plan with three small wins that unblock bigger work. Background first, then exact tasks you can ship in a day.
1) Scanner post-mortem → 2 reproducible regressions
Why: Post-mortems sprawl. Two bullet-proof repros turn theories into fixable tickets.
Task today
- Pick the two highest-impact failure modes (e.g., wrong reachability verdict; missed OSV/CVE due to parser).
- For each, produce a 1-command repro script (Docker image + SBOM fixture) and an expected vs actual JSON artifact.
- Store under
tests/scanner/regressions/<case-id>/withREADME.mdandmake testtarget.
Definition of done
- CI job
Scanner-Regressionruns both and fails deterministically if behavior regresses.
2) Mongo→Postgres slice prototype (pick one)
Why: A focused end-to-end slice beats a giant migration plan.
Candidates
Authority.Tokens(licensing/entitlements)Scheduler.Jobs(enqueued/executed metadata)VEX.Verdicts(append-only history)
Task today
- Draft minimal DDL in
db/pg/migrations/0001_<slice>.sqland EF Core model. - Add writer + reader paths behind
I<Slice>Store; toggle backend usingSTORE_BACKEND=Mongo|Postgres. - Compose a data-parity test: write N records → read from both stores → byte-compare normalized JSON.
Definition of done
- Service boots with
STORE_BACKEND=Postgres, parity test passes locally, migration cleanly creates/drops the slice.
3) DSSE + Rekor attestation batch sweep
Plain English: sign DSSE envelopes, submit to Rekor, and size batches until failures appear.
Task today
- Choose an artifact type (e.g.,
scanner-report.json). - Implement a tiny CLI/script that signs K reports into DSSE, submits to Rekor/mirror, and logs latency + bytes + success.
- Sweep K in {1,5,10,25,50}, output CSV under
experiments/dsse_rekor/.
Definition of done
- Deliver plot/table with recommended batch size and failure threshold (e.g., “use 10; >25 implies 8% retries”).
Suggested assignments (1-day scope)
- Alex → #1 repros
- Mira → #2 PG slice (Authority.Tokens)
- Petar → #3 DSSE/Rekor sweep
Ready-to-copy tickets
- SCN-201: “Create 2 deterministic regression tests for Scanner failures” (include folder layout + CI target).
- DB-301: “Prototype Postgres backend for Authority.Tokens with parity test and feature flag.”
- SEC-411: “Run DSSE+Rekor batch sweep for scanner reports; publish CSV and default threshold.”
If you want, I can generate the folders, Makefiles, EF Core stubs, and DSSE/Rekor sweep script next. Good, this helps narrow the scope. I’ll go point by point and then expand #3.
1) How these fit into Stella Ops, concretely
Think in terms of your core promises / moats:
- Deterministic, replayable scans
- Verifiable SBOM → VEX chain with proofs
- Future-proof data layout (Mongo + “recognized” SQL)
- Proof-of-integrity / Rekor-style transparency
The three tasks line up like this:
Task 1: Scanner post-mortem → 2 reproducible test cases
Where it lives: scanner.webservice + your test suite.
Why it matters for Stella Ops:
-
Your brand is: “we don’t silently miss things; if we ever do, it becomes a test forever”.
-
Each “failure mode” becomes a fixture that:
- feeds into reachability heuristics,
- is later used by Vexer/Excititor to show “we’ve seen this pattern in the wild and we handle it”.
It’s not random QA; it’s the foundation for your “golden reachability dataset” idea from the other branch. Every time you study a competitor miss or your own miss, it turns into:
- “Golden fixture #NN – known tricky case, guarded forever by CI”.
So this is directly supporting:
- Deterministic scanner behavior
- Trust Algebra Studio later being able to say: “policy X passes all N golden fixtures”.
Very practical outcome: it gives your devs a concrete target instead of a vague “scanner is sometimes wrong”.
Task 2: Postgres without migration: why and how
You’re right: there is no migration today, only shape-finding.
You said earlier: “conversion, not migration” and “we use PostgreSQL mainly because of recognition”. That can be turned into something useful now, without over-engineering:
Goal in this phase
-
Define one slice of data model in Postgres that:
- is understandable to auditors / integrators,
- is stable enough that, if you later decide to “convert” Mongo → PG, you already know how it should look,
- forces you to create a clean abstraction (
IWhateverStore) rather than hard-wiring Mongo into every service.
So instead of “migration plan”, think:
“We are prototyping a Postgres-friendly façade for one core concept, behind an interface.”
Example: Authority.Tokens or Scheduler.Jobs.
- You keep Mongo as the actual source of truth for now.
- You add a minimal Postgres model in EF Core.
- You add a parity test (write/read in both backends, compare).
- You wire a feature flag like
STORE_BACKEND=Mongo|Postgresso you can switch environments on/off.
This gives you:
- Early signal about “does this data model work in SQL?”
- A future-proof seam where “conversion” can happen when the product stabilizes.
- Something that looks familiar to enterprise customers (“yes, we have Postgres, here is the schema”).
No migration script, no DMS, just learning and shaping.
If you prefer, you can drop the CI parity test and only keep:
- Interface
- Two implementations
- A simple console test or integration test
to keep ceremony minimal while still forcing a clean boundary.
3) DSSE + Rekor attestation experiment: deeper elaboration
I’ll treat this as: “Explain what exactly my team should build and why it matters to Stella Ops.”
3.1. Why you care at all
This task supports at least three of your moats:
- Deterministic replayable scans The DSSE envelope + Rekor entry is a cryptographic “receipt” for a given scan + SBOM + VEX result.
- Proof-of-Integrity Graph / Proof-Market Ledger If you later build your own Rekor mirror or “Proof-Market Ledger”, you need to know real batch sizes and behavior now.
- Crypto-sovereign readiness Eventually you want GOST / SM / PQC signatures; this small experiment tells you how your stack behaves with any signature scheme you plug in later.
So we’re doing one focused measurement:
For one type of attestation, find the smallest batch size that:
- keeps latency acceptable,
- doesn’t cause excessive timeouts or retries,
- doesn’t make envelopes so large they become awkward.
This becomes your default configuration for Scanner → Attestation → Rekor in all future designs.
3.2. What exactly to build
Propose a tiny .NET 10 console tool, e.g.:
src/Experiments/StellaOps.Attest.Bench/StellaOps.Attest.Bench.csproj
Binary: stella-attest-bench
Inputs
- A directory with scanner reports, e.g.:
artifacts/scanner-reports/*.json - Rekor endpoint and credentials (or test/mirror instance)
- Batch sizes to sweep: e.g.
1,5,10,25,50
CLI sketch
stella-attest-bench \
--reports-dir ./artifacts/scanner-reports \
--rekor-url https://rekor.stella.local \
--batch-sizes 1,5,10,25,50 \
--out ./experiments/dsse_rekor/results.csv
What each run does
For each batch size K:
-
Take
Kreports from the directory. -
For each report:
-
Wrap into a DSSE envelope:
{ "payloadType": "application/vnd.stellaops.scanner-report+json", "payload": "<base64(report.json)>", "signatures": [ { "keyid": "authority-key-1", "sig": "<base64(signature-by-key-1)>" } ] } -
Measure size of the envelope in bytes.
-
-
Submit the
Kenvelopes to Rekor:-
Either one by one, or if your client API supports it, in a single batch call.
-
Record:
- start timestamp
- end timestamp
- status (success / failure / retry count)
-
-
Append a row to
results.csv:timestamp,batch_size,envelopes_count,total_bytes,avg_bytes,latency_ms,successes,failures,retries 2025-11-30T14:02:00Z,10,10,123456,12345.6,820,10,0,0
You can enrich it later with HTTP codes, Rekor log index, etc., but this is enough to choose a default.
3.3. Minimal internal structure
Rough C# layout (no full code, just architecture so devs don’t wander):
// Program.cs
// - Parse args
// - Build IServiceProvider
// - Resolve and run BenchRunner
public sealed class BenchConfig
{
public string ReportsDirectory { get; init; } = default!;
public Uri RekorUrl { get; init; } = default!;
public int[] BatchSizes { get; init; } = Array.Empty<int>();
public string OutputCsvPath { get; init; } = default!;
}
public sealed class BenchRunner
{
private readonly IDsseSigner _signer;
private readonly IRekorClient _rekor;
private readonly IResultsSink _sink;
public async Task RunAsync(BenchConfig config, CancellationToken ct);
}
// IDsseSigner: wraps "payload" (report json bytes) -> DSSE envelope bytes
// IRekorClient: envelope bytes -> result (success/fail, latency, retries)
// IResultsSink: append rows to CSV
Later, IDsseSigner and IRekorClient can be reused inside a proper StellaOps.Attest library that the Scanner or Scheduler use.
3.4. What “done” looks like in a way that matters to Stella Ops
Not just “we have a toy”. You want a decision out of this:
-
DEFAULT_DSSE_BATCH_SIZE = 10 -
“For this Rekor setup, we observe:
- ≤10: 100 % success, latency ~X ms
- 25: occasional retries
- 50: frequent 5xx / timeouts; not recommended”
You then:
-
Write this into a short markdown note, e.g.
docs/attestation/dsse-rekor-batch-sizing.md -
Use it when designing:
- Scanner → “Attestation job” messages (avoid pushing >10 envelopes at once),
- The future “Proof-Market Ledger” so it doesn’t assume unbounded batches,
- Air-gap mirror behavior (“we queue up batches of 10 before syncing”).
So the outcome is a concrete configuration default and a constraint that all future modules respect.
If you want to simplify further
If this still feels too heavy for current prototyping speed, the absolute minimum to keep the value:
-
Hardcode 2 batch sizes:
1and10. -
Only measure:
- latency
- success/fail
-
Print results to stdout instead of CSV.
The key is: one small executable experiment that turns “DSSE + Rekor is cool” into “our default batch size is X for environment Y”.
If you tell me which module names you want to standardize on for this (e.g. StellaOps.Attest, StellaOps.Experiments, etc.), I can sketch exact folder names, namespaces, and even a first .csproj + Program.cs your dev can paste in.
I’ll rewrite all three as tight internal guidelines you can drop into docs/dev/. Think of each as something you can hand to a new engineer and say: “Follow this exactly.”
G1 · Scanner regression fixtures
File: docs/dev/scanner-regressions.md
Applies to: scanner.webservice and any libraries it uses.
1. Purpose
Any serious scanner failure must become a deterministic regression fixture. Over time this becomes Stella Ops’ “golden dataset” for reachability and correctness.
Outcomes:
- Bugs don’t repeat silently.
- Heuristics and future policies are validated against the same corpus.
- Post‑mortems always end with a guardrail in CI.
2. When to create a regression fixture
Create a fixture when all three hold:
-
The bug affects at least one of:
- Severity ≥ Medium
- High‑volume ecosystems (OS packages, Java, Python, Node, container base images)
- Core behaviors (reachability, deduplication, suppression, parser correctness)
-
The behavior is reproducible from static inputs (image, SBOM, or manifests).
-
The expected correct behavior is agreed by at least one more engineer on the scanner team.
If in doubt: add the fixture. It is cheap, and it strengthens the golden corpus.
3. Directory structure & naming
Test project:
tests/
StellaOps.Scanner.RegressionTests/
Regression/
SCN-0001-missed-cve-in-layer/
SCN-0002-wrong-reachability/
...
Fixture layout (example):
SCN-0001-missed-cve-in-layer/
input/
image.sbom.json # or image.tar, etc.
config.json # scanner flags, if needed
expected/
findings.json # canonical expected findings
case.metadata.json # machine-readable description
case.md # short human narrative
case.metadata.json schema:
{
"id": "SCN-0001",
"title": "Missed CVE-2025-12345 in lower layer",
"kind": "vulnerability-missed",
"source": "internal-postmortem",
"severity": "high",
"tags": [
"reachability",
"language:java",
"package:log4j"
]
}
case.md should answer:
- What failed?
- Why this case is representative / important?
- What is the correct expected behavior?
4. Test harness rules
Global rules for all regression tests:
- No network access (fixtures must be fully self‑contained).
- No time‑dependent logic (use fixed timestamps if necessary).
- No nondeterministic behavior (seed any randomness).
Comparison rules:
-
Normalize scanner output before comparison:
- Sort arrays (e.g. findings).
- Remove volatile fields (generated IDs, timestamps, internal debug metadata).
-
Compare canonical JSON (e.g. string equality on normalized JSON).
Implementation sketch (xUnit):
public class GoldenRegressionTests
{
[Theory]
[MemberData(nameof(RegressionSuite.LoadCases), MemberType = typeof(RegressionSuite))]
public async Task Scanner_matches_expected_findings(RegressionCase @case)
{
var actual = await ScannerTestHost.RunAsync(@case.InputDirectory);
var expectedJson = File.ReadAllText(@case.ExpectedFindingsPath);
var normalizedActual = FindingsNormalizer.Normalize(actual);
var normalizedExpected = FindingsNormalizer.Normalize(expectedJson);
Assert.Equal(normalizedExpected, normalizedActual);
}
}
5. How to add a new regression case
Checklist for developers:
-
Reproduce the bug using local dev tools.
-
Minimize the input:
- Prefer a trimmed SBOM or minimal image over full customer artifacts.
- Remove sensitive data; use synthetic equivalents if needed.
-
Create folder under
Regression/SCN-XXXX-short-slug/. -
Populate:
input/with all needed inputs (SBOMs, manifests, config).expected/findings.jsonwith the correct canonical output (not the buggy one).case.metadata.jsonandcase.md.
-
Run tests locally:
dotnet test tests/StellaOps.Scanner.RegressionTests -
Fix the scanner behavior (if not already fixed).
-
Ensure tests fail without the fix and pass with it.
-
Open PR with:
- Fixture directory.
- Scanner fix.
- Any harness adjustments.
6. CI integration & “done” definition
-
CI job
Scanner-Regressionruns in PR validation and main. -
A regression case is “live” when:
- It’s present under
Regression/. - It is picked up by the harness.
- CI fails if the behavior regresses.
- It’s present under
G2 · Postgres slice prototype (shape‑finding, no migration)
File: docs/dev/authority-store-backends.md
Applies to: Authority (or similar) services that currently use Mongo.
1. Purpose
This is not data migration. It is about:
- Designing a clean storage interface.
- Prototyping a Postgres‑friendly schema for one bounded slice (e.g.
Authority.Tokens). - Being able to run the service with either Mongo or Postgres behind the same interface.
This supports future “conversion” and enterprise expectations (“we speak Postgres”) without blocking current prototyping speed.
2. Scope & constraints
- Scope: one slice only (e.g. tokens, jobs, or VEX verdicts).
- Mongo remains the operational source of truth for now.
- Postgres path is opt‑in via configuration.
- No backward migration or synchronization logic.
3. Repository layout
Example for Authority.Tokens:
src/
StellaOps.Authority/
Domain/
Token.cs
TokenId.cs
...
Stores/
ITokenStore.cs
MongoTokenStore.cs
PostgresTokenStore.cs
Persistence/
AuthorityPgDbContext.cs
Migrations/
0001_AuthTokens_Init.sql
docs/
dev/
authority-store-backends.md
4. Store interface design
Guidelines:
- Keep the interface narrow and domain‑centric.
- Do not leak Mongo or SQL constructs.
Example:
public interface ITokenStore
{
Task<Token?> GetByIdAsync(TokenId id, CancellationToken ct = default);
Task<IReadOnlyList<Token>> GetByOwnerAsync(PrincipalId ownerId, CancellationToken ct = default);
Task SaveAsync(Token token, CancellationToken ct = default);
Task RevokeAsync(TokenId id, RevocationReason reason, CancellationToken ct = default);
}
Both MongoTokenStore and PostgresTokenStore must implement this contract.
5. Postgres schema guidelines
Principles:
- Use schemas per bounded context, e.g.
authority. - Choose stable primary keys (
uuidorbigint), not composite keys unless necessary. - Index only for known access patterns.
Example DDL:
CREATE SCHEMA IF NOT EXISTS authority;
CREATE TABLE IF NOT EXISTS authority.tokens (
id uuid PRIMARY KEY,
owner_id uuid NOT NULL,
kind text NOT NULL,
issued_at_utc timestamptz NOT NULL,
expires_at_utc timestamptz NULL,
is_revoked boolean NOT NULL DEFAULT false,
revoked_at_utc timestamptz NULL,
revoked_reason text NULL,
payload_json jsonb NOT NULL,
created_at_utc timestamptz NOT NULL DEFAULT now(),
updated_at_utc timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS ix_tokens_owner_id
ON authority.tokens (owner_id);
CREATE INDEX IF NOT EXISTS ix_tokens_kind
ON authority.tokens (kind);
EF Core:
- Map with
ToTable("tokens", "authority"). - Use owned types or value converters for
payload_json.
6. Configuration & feature flag
Configuration:
// appsettings.json
{
"Authority": {
"StoreBackend": "Mongo", // or "Postgres"
"Postgres": {
"ConnectionString": "Host=...;Database=stella;..."
},
"Mongo": {
"ConnectionString": "...",
"Database": "stella_authority"
}
}
}
Environment override:
AUTHORITY_STORE_BACKEND=Postgres
DI wiring:
services.AddSingleton<ITokenStore>(sp =>
{
var cfg = sp.GetRequiredService<IOptions<AuthorityOptions>>().Value;
return cfg.StoreBackend switch
{
"Postgres" => new PostgresTokenStore(
sp.GetRequiredService<AuthorityPgDbContext>()),
"Mongo" => new MongoTokenStore(
sp.GetRequiredService<IMongoDatabase>()),
_ => throw new InvalidOperationException("Unknown store backend")
};
});
7. Minimal parity / sanity checks
Given you are still prototyping, keep this light.
Recommended:
-
A single test or console harness that:
- Creates a small set of
Tokenobjects. - Writes via Mongo and via Postgres.
- Reads back from each and compares a JSON representation of the domain object (ignoring DB‑specific metadata).
- Creates a small set of
This is not full data migration testing; it is a smoke check that both backends honor the same domain contract.
8. “Done” definition for the first slice
For the chosen slice (e.g. Authority.Tokens):
-
ITokenStoreexists, with Mongo and Postgres implementations. -
A Postgres DbContext and first migration are present and runnable.
-
The service starts cleanly with
StoreBackend=Postgresagainst an empty DB. -
There is at least one automated or scripted sanity check that both backends behave equivalently for typical operations.
-
This is documented in
docs/dev/authority-store-backends.md:- How to switch backends.
- Current state: “Postgres is experimental; Mongo is default.”
G3 · DSSE + Rekor batch‑size experiment
File: docs/attestation/dsse-rekor-batch-sizing.md
Applies to: Attestation / integrity pipeline for scanner reports.
1. Purpose
Determine a concrete default batch size for DSSE envelopes submitted to Rekor, balancing:
- Reliability (few or no failures / retries).
- Latency (per batch).
- Envelope size (practical for transport and logging).
Outcome: a hard configuration value, e.g. DefaultDsseRekorBatchSize = 10, backed by measurement.
2. Scope
- Artifact type: scanner report (e.g.
scanner-report.json). - Environment: your current Rekor endpoint (or mirror), not production critical path.
- One small experiment tool, later reusable by attestation services.
3. Project structure
src/
Experiments/
StellaOps.Attest.Bench/
Program.cs
BenchConfig.cs
BenchRunner.cs
DsseSigner.cs
RekorClient.cs
ResultsCsvSink.cs
experiments/
dsse_rekor/
results.csv
docs/
attestation/
dsse-rekor-batch-sizing.md
4. CLI contract
Binary: stella-attest-bench
Example usage:
stella-attest-bench \
--reports-dir ./artifacts/scanner-reports \
--rekor-url https://rekor.lab.stella \
--batch-sizes 1,5,10,25,50 \
--out ./experiments/dsse_rekor/results.csv \
--max-retries 3 \
--timeout-ms 10000
Required flags:
--reports-dir--rekor-url--batch-sizes--out
Optional:
--max-retries(default 3)--timeout-ms(default 10000 ms)
5. Implementation guidelines
Core config:
public sealed class BenchConfig
{
public string ReportsDirectory { get; init; } = default!;
public Uri RekorUrl { get; init; } = default!;
public int[] BatchSizes { get; init; } = Array.Empty<int>();
public string OutputCsvPath { get; init; } = default!;
public int MaxRetries { get; init; } = 3;
public int TimeoutMs { get; init; } = 10_000;
}
Interfaces:
public interface IDsseSigner
{
byte[] WrapAndSign(byte[] payload, string payloadType);
}
public interface IRekorClient
{
Task<RekorSubmitResult> SubmitAsync(
IReadOnlyList<byte[]> envelopes,
CancellationToken ct);
}
public interface IResultsSink
{
Task AppendAsync(BatchMeasurement measurement, CancellationToken ct);
}
Measurement model:
public sealed class BatchMeasurement
{
public DateTime TimestampUtc { get; init; }
public int BatchSize { get; init; }
public int EnvelopeCount { get; init; }
public long TotalBytes { get; init; }
public double AverageBytes { get; init; }
public long LatencyMs { get; init; }
public int SuccessCount { get; init; }
public int FailureCount { get; init; }
public int RetryCount { get; init; }
}
Runner outline:
-
Enumerate report files in
ReportsDirectory(e.g.*.json). -
For each
batchSize:-
Select up to
batchSizereports. -
For each report:
- Read bytes.
- Call
IDsseSigner.WrapAndSignwith payload typeapplication/vnd.stellaops.scanner-report+json. - Track envelope sizes.
-
Start stopwatch.
-
Submit via
IRekorClient.SubmitAsyncwith retry logic honoringMaxRetriesandTimeoutMs. -
Record latency, successes, failures, retries.
-
Write one
BatchMeasurementrow toresults.csv.
-
CSV headers:
timestamp_utc,batch_size,envelopes_count,total_bytes,avg_bytes,latency_ms,successes,failures,retries
Signer:
- For the experiment, use a local dev key (e.g. Ed25519).
- Make signing deterministic (no random salts that affect envelope size unexpectedly).
Rekor client:
- For this experiment, you only need “accepted or not” plus HTTP status codes; inclusion proof verification is out of scope.
6. How to run and interpret
Execution steps:
-
Prepare a folder with representative scanner reports:
- At least 20–30 reports from different images, to approximate typical size variance.
-
Ensure the Rekor environment is reachable and not rate‑limited.
-
Run the tool with a sweep like
1,5,10,25,50. -
Inspect
results.csv:-
For each batch size, look at:
- Average latency.
- Any non‑zero failures or elevated retry counts.
- Total and average bytes per envelope.
-
Decision rules:
-
Pick the largest batch size that:
- Shows 0 failures and acceptable retry counts across multiple runs.
- Keeps latency in a reasonable budget for your pipeline (e.g. under a few seconds per batch).
-
Define:
DefaultBatchSize= chosen safe value.HardMaxBatchSize= first size where failures or unacceptable latency appear.
Document in docs/attestation/dsse-rekor-batch-sizing.md:
-
Environment details (Rekor version, hardware, network).
-
Brief description of report corpus.
-
A summarized table from
results.csv. -
The chosen
DefaultBatchSizeandHardMaxBatchSizewith a clear statement:- “All production attestation jobs must respect this default and must not exceed the hard max without a new measurement.”
7. “Done” definition
The experiment is “done” when:
-
StellaOps.Attest.Benchbuilds and runs with the specified CLI. -
It produces a
results.csvwith one row per(run, batch_size)combination. -
There is a written default:
DefaultDsseRekorBatchSize = X- plus rationale in
dsse-rekor-batch-sizing.md.
-
Future attestation designs (e.g. scanner → ledger pipeline) are required to use that default unless they explicitly update the experiment and the document.
If you want, next step can be: convert each of these into actual files (including a minimal csproj and Program.cs for the bench tool) so your team can copy‑paste and start implementing.