up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
88
docs/modules/concelier/events/advisory.linkset.updated@1.md
Normal file
88
docs/modules/concelier/events/advisory.linkset.updated@1.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# advisory.linkset.updated@1 · Event contract
|
||||
|
||||
Purpose: unblock CONCELIER-LNM-21-005 by freezing the platform event shape for linkset changes emitted by Concelier. This is the only supported event for linkset churn; downstreams subscribe for graph overlays, policy evaluations, and replay bundles.
|
||||
|
||||
## Envelope & transport
|
||||
- Subject: `concelier.advisory.linkset.updated.v1`
|
||||
- Type/version: `advisory.linkset.updated@1`
|
||||
- Transport: NATS (primary), Redis Stream `concelier:advisory.linkset.updated:v1` (fallback). Both carry the same DSSE envelope.
|
||||
- DSSE payloadType: `application/vnd.stellaops.advisory.linkset.updated.v1+json`.
|
||||
- Signature: Ed25519 via Platform Events signer; attach Rekor UUID when available. Offline kits treat the envelope as the source of truth.
|
||||
|
||||
## Payload (JSON)
|
||||
| Field | Type | Rules |
|
||||
| --- | --- | --- |
|
||||
| `eventId` | string (uuid) | Generated by publisher; idempotency key. |
|
||||
| `tenantId` | string | `urn:tenant:{uuid}`; required for multi-tenant routing. |
|
||||
| `linksetId` | string (ObjectId) | Mongo `_id` of the linkset document. |
|
||||
| `advisoryId` | string | Upstream advisory identifier (e.g., CVE, GHSA, vendor id). |
|
||||
| `source` | string | Linkset source/adapter identifier (lowercase). |
|
||||
| `observationIds` | string[] | Array of observation ObjectIds included in this linkset; sorted ASCII. |
|
||||
| `delta` | object | Change description: `{ type, observationsAdded, observationsRemoved, confidenceChanged, conflictsChanged }`. |
|
||||
| `confidence` | number (0–1, nullable) | Correlation confidence score; null if not computed. |
|
||||
| `conflicts` | object[] | Array of `{ field, reason, sourceIds[] }` conflict summaries; sorted by field then reason. |
|
||||
| `provenance` | object | `{ observationHashes[], toolVersion?, policyHash? }` for replay/audit. |
|
||||
| `createdAt` | string (ISO-8601 UTC) | Timestamp when linkset was built. |
|
||||
| `replayCursor` | string | Monotone cursor for offline bundle ordering (tick from createdAt). |
|
||||
| `builtByJobId` | string (optional) | Job ID that built this linkset. |
|
||||
| `traceId` | string (optional) | Propagated from ingest job/request; aids join with logs/metrics. |
|
||||
|
||||
### Delta object
|
||||
| Field | Type | Rules |
|
||||
| --- | --- | --- |
|
||||
| `type` | string | `"created"` or `"updated"`. |
|
||||
| `observationsAdded` | string[] | Observation IDs added since previous version. |
|
||||
| `observationsRemoved` | string[] | Observation IDs removed since previous version. |
|
||||
| `confidenceChanged` | boolean | True if confidence score changed. |
|
||||
| `conflictsChanged` | boolean | True if conflicts array changed. |
|
||||
|
||||
### Determinism & ordering
|
||||
- Arrays sorted ASCII; objects field-sorted when hashing.
|
||||
- `eventId` + `replayCursor` provide exactly-once consumer handling; duplicates must be ignored when observation hashes unchanged.
|
||||
- No judgments: only raw facts, delta descriptions, and provenance pointers; any derived severity/merge content is forbidden.
|
||||
|
||||
### Error contracts for Scheduler
|
||||
- Retryable NATS/Redis failures use backoff capped at 30s; after 5 attempts, emit `concelier.events.dlq` with the same envelope and `error` field describing transport failure.
|
||||
- Consumers must NACK on schema validation failure; publisher logs `ERR_EVENT_SCHEMA` and quarantines the offending linkset id.
|
||||
|
||||
## Sample payload
|
||||
```json
|
||||
{
|
||||
"eventId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"tenantId": "urn:tenant:acme-corp",
|
||||
"linksetId": "6744abcd1234567890abcdef",
|
||||
"advisoryId": "CVE-2024-1234",
|
||||
"source": "nvd",
|
||||
"observationIds": ["674400001234567890abcdef", "674400001234567890abcde0"],
|
||||
"delta": {
|
||||
"type": "created",
|
||||
"observationsAdded": ["674400001234567890abcdef", "674400001234567890abcde0"],
|
||||
"observationsRemoved": [],
|
||||
"confidenceChanged": true,
|
||||
"conflictsChanged": false
|
||||
},
|
||||
"confidence": 0.85,
|
||||
"conflicts": [],
|
||||
"provenance": {
|
||||
"observationHashes": ["sha256:abc123", "sha256:def456"],
|
||||
"toolVersion": "1.0.0",
|
||||
"policyHash": null
|
||||
},
|
||||
"createdAt": "2025-11-27T12:00:00.000Z",
|
||||
"replayCursor": "638688000000000000",
|
||||
"builtByJobId": "job-12345",
|
||||
"traceId": "trace-67890"
|
||||
}
|
||||
```
|
||||
|
||||
## Schema
|
||||
`advisory.linkset.updated@1.schema.json` provides a JSON Schema (draft 2020-12) for runtime validation; any additional fields are rejected.
|
||||
|
||||
## Implementation (LNM-21-005)
|
||||
- Event type defined in `StellaOps.Concelier.Core.Linksets.AdvisoryLinksetUpdatedEvent`.
|
||||
- Publisher interface: `IAdvisoryLinksetEventPublisher`.
|
||||
- Outbox interface: `IAdvisoryLinksetEventOutbox`.
|
||||
- Configuration: `AdvisoryLinksetEventPublisherOptions`.
|
||||
|
||||
## Change control
|
||||
- Add-only. Adjusting delta types or conflict codes requires new version `advisory.linkset.updated@2` and a sprint note.
|
||||
@@ -78,6 +78,33 @@ _Frozen v1 (add-only) — approved 2025-11-17 for CONCELIER-LNM-21-001/002/101._
|
||||
- **Deterministic keying:** `_id` derived from `hash(tenantId|source|advisoryId|provenance.sourceArtifactSha)` to keep inserts idempotent in replay.
|
||||
- **Normalization guardrails:** version ranges must be stored as raw-from-source; no inferred merges.
|
||||
|
||||
## Append-Only Contract (AOC) — LNM-21-004
|
||||
|
||||
The Aggregation-Only Contract (AOC) ensures observations are immutable after creation. This is enforced by `IAdvisoryObservationWriteGuard`.
|
||||
|
||||
### Write disposition rules
|
||||
| Existing Hash | New Hash | Disposition | Action |
|
||||
|--------------|----------|-------------|--------|
|
||||
| null/empty | any | `Proceed` | Insert new observation |
|
||||
| X | X (identical) | `SkipIdentical` | Idempotent re-insert, no write |
|
||||
| X | Y (different) | `RejectMutation` | Reject with `AppendOnlyViolationException` |
|
||||
|
||||
### Supersession model
|
||||
When an advisory source publishes a revised version of an advisory:
|
||||
1. A **new observation** is created with its own unique `observationId` and `contentHash`.
|
||||
2. The new observation MAY carry a `supersedesId` pointing to the previous observation.
|
||||
3. The **original observation remains immutable** — it is never updated or deleted.
|
||||
4. Linksets are rebuilt to include all non-superseded observations; superseded observations remain queryable for audit but excluded from active linkset aggregation.
|
||||
|
||||
### Implementation checklist (LNM-21-004)
|
||||
- [x] `IAdvisoryObservationWriteGuard` interface with `ValidateWrite(observation, existingContentHash)` method.
|
||||
- [x] `AdvisoryObservationWriteGuard` implementation enforcing append-only semantics.
|
||||
- [x] `AppendOnlyViolationException` for mutation rejections.
|
||||
- [x] DI registration via `AddConcelierAocGuards()` extension.
|
||||
- [x] Unit tests covering Proceed/SkipIdentical/RejectMutation scenarios.
|
||||
- [x] Legacy merge logic deprecated with `[Obsolete]` and gated by `NoMergeEnabled` feature flag (defaults to `true`).
|
||||
- [x] Roslyn analyzer `StellaOps.Concelier.Analyzers.NoMergeApiAnalyzer` emits warnings for merge API usage.
|
||||
|
||||
## Linkset document
|
||||
```json
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user