Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 53 additions & 29 deletions .github/scripts/verify-packed-retrieval-parity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ function assertPackageMeasurement(record, tarballPath) {
'utf8',
))
const evaluationTooling = manifest.items?.find((item) => item.id === 'evaluation-tooling')
const budget = evaluationTooling?.npm_package_budget
const activePhase = manifest.items?.find((item) => item.id === manifest.current?.active_phase)
const budget = activePhase?.npm_package_budget ?? evaluationTooling?.npm_package_budget
const receipt = manifest.current
const actual = {
npm_files: requiredNumber(record.entryCount, 'npm pack entryCount'),
Expand Down Expand Up @@ -85,14 +86,14 @@ function assertPackageMeasurement(record, tarballPath) {
)
}
if (
actual.npm_files > requiredNumber(budget?.files_max, 'Evaluation Tooling files_max')
actual.npm_files > requiredNumber(budget?.files_max, 'active files_max')
|| actual.npm_packed_bytes
> requiredNumber(budget?.packed_bytes_max, 'Evaluation Tooling packed_bytes_max')
> requiredNumber(budget?.packed_bytes_max, 'active packed_bytes_max')
|| actual.npm_unpacked_bytes
> requiredNumber(budget?.unpacked_bytes_max, 'Evaluation Tooling unpacked_bytes_max')
> requiredNumber(budget?.unpacked_bytes_max, 'active unpacked_bytes_max')
) {
throw new Error(
`Fresh npm package exceeds Evaluation Tooling budgets: ${JSON.stringify(actual)}`,
`Fresh npm package exceeds the active package budget: ${JSON.stringify(actual)}`,
)
}
return actual
Expand Down Expand Up @@ -240,11 +241,18 @@ function successfulRetrieve(response, label, expectedLabels) {
} catch {
throw new Error(`${label} did not return canonical JSON evidence`)
}
if (result?.schema !== 'madar.retrieve' || result?.outcome !== 'evidence') {
throw new Error(`${label} did not complete successful evidence retrieval`)
if (
result?.schema !== 'madar.retrieve'
|| result?.version !== 2
|| result?.state !== 'ready'
|| !result?.dossier?.evidence
) {
throw new Error(`${label} did not complete a ready v2 dossier retrieval`)
}
const labels = new Set((result.matched_nodes ?? []).map((node) =>
String(node.label ?? '').replaceAll(/[^a-z0-9]/gi, '').toLowerCase()))
const labels = new Set(result.dossier.evidence.entities
.filter((entity) => entity.kind === 'symbol')
.map((entity) => String(entity.label ?? '')
.replaceAll(/[^a-z0-9]/gi, '').toLowerCase()))
for (const expected of expectedLabels) {
if (!labels.has(expected.toLowerCase())) {
throw new Error(
Expand Down Expand Up @@ -451,7 +459,7 @@ try {
name: 'retrieve',
arguments: {
question: 'How is an idea report generated? Explain the pipeline flow from request to final report.',
budget: 8_000,
budget: 4_000,
},
},
}
Expand Down Expand Up @@ -490,15 +498,14 @@ try {
const flowResult = successfulRetrieve(packedFlow, 'Packed full-flow runtime', [
'generatefromproblem',
'startpipeline',
'enqueuejob',
'plan',
'researchsection',
'assemblereport',
'savestructuredreport',
])
const expectedFlowFiles = [
'src/modules/ideas/interface/http/idea-generation.controller.ts',
'src/modules/pipeline/api/pipeline-trigger.service.ts',
'src/modules/pipeline/api/queue-registry.service.ts',
'src/modules/pipeline/workers/orchestrator.worker.ts',
'src/modules/planning/planner.service.ts',
'src/modules/research/workers/section-research.worker.ts',
Expand All @@ -507,26 +514,43 @@ try {
'src/modules/reports/assembly.service.ts',
'src/modules/pipeline/workers/db-sync.worker.ts',
]
const actualFlowFiles = flowResult.matched_nodes?.map((node) => node.source_file)
const expectedBoundaries = [
'src/modules/planning/planner.service.ts:L13-L16 -> src/modules/research/workers/section-research.worker.ts:L17-L19',
'src/modules/research/research-agent.service.ts:L10-L14 -> src/modules/pipeline/assembly/assembly.worker.ts:L17-L19',
'src/modules/reports/assembly.service.ts:L20-L31 -> src/modules/pipeline/workers/db-sync.worker.ts:L26-L35',
]
const actualBoundaries = flowResult.boundaries
?.filter((boundary) => boundary.kind === 'disconnected')
.map((boundary) => boundary.detail)
const actualFlowFiles = flowResult.dossier.evidence.files.map(({ path }) => path).sort()
const channelLinks = flowResult.dossier.flow.links.filter(({ kind }) => kind === 'channel')
const flowProofs = new Map(flowResult.dossier.evidence.proofs.map((proof) => [proof.id, proof]))
const obligationKinds = flowResult.dossier.obligations.map(({ kind }) => kind)
const hasPersistenceProof = flowResult.dossier.evidence.entities.some((entity) =>
entity.kind === 'operation' && entity.operation_kind === 'persistence')
if (
JSON.stringify(actualFlowFiles) !== JSON.stringify(expectedFlowFiles)
|| !expectedBoundaries.every((boundary) => actualBoundaries?.includes(boundary))
|| flowResult.relationships?.length === 0
JSON.stringify(actualFlowFiles) !== JSON.stringify(expectedFlowFiles.sort())
|| JSON.stringify(obligationKinds) !== JSON.stringify([
'subject', 'entry', 'stage', 'handoff', 'behavior', 'ordering', 'terminal',
])
|| flowResult.dossier.obligations.some(({ proofs }) => proofs.length === 0)
|| channelLinks.length !== 4
|| channelLinks.some(({ proofs }) => {
const relations = proofs.map((proof) => flowProofs.get(proof)?.relation)
const publishAt = relations.indexOf('publishes_to')
return publishAt < 0
|| !relations.slice(0, publishAt).every((relation) => relation === 'calls')
|| ![
JSON.stringify(['publishes_to', 'consumed_by']),
JSON.stringify(['publishes_to', 'routes_through', 'consumed_by']),
].includes(JSON.stringify(relations.slice(publishAt)))
})
|| flowResult.dossier.flow.terminals.length === 0
|| !hasPersistenceProof
|| flowResult.metrics?.selected_files > 12
|| flowResult.metrics?.snippets > 25
|| flowResult.metrics?.closure_passes > 1
|| flowResult.metrics?.authenticated_excerpts > 25
|| flowResult.metrics?.root_candidates > 3
|| flowResult.metrics?.initial_candidates > 32
|| flowResult.metrics?.explored_nodes > 512
|| flowResult.metrics?.causal_hops > 24
|| flowResult.metrics?.recovery_passes > 2
|| flowResult.metrics?.recovery_frontier_nodes > 64
|| flowResult.metrics?.alternate_seeds > 3
|| flowResult.metrics?.serialized_tokens > 4_000
|| flowResult.metrics?.truncated !== false
) {
throw new Error(`Packed full-flow evidence violated #622: ${JSON.stringify(flowResult)}`)
throw new Error(`Packed full-flow dossier violated #630: ${JSON.stringify(flowResult)}`)
}

const workerRoot = join(tempRoot, 'worker-workspace')
Expand Down Expand Up @@ -582,7 +606,7 @@ try {
method: 'tools/call',
params: {
name: 'retrieve',
arguments: { question: 'What is value0?' },
arguments: { question: 'Where is value0 defined?' },
},
})}\n`)
input.write(`${JSON.stringify({ jsonrpc: '2.0', id: 702, method: 'ping' })}\n`)
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ jobs:
- name: Build evaluation tooling
run: npm run build:eval

- name: Enforce frozen issue 630 obligation dossier benchmark
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '22'
run: node tools/eval/core-reset/benchmark.mjs

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Validate Core Reset evidence contract
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '22'
run: npx vitest run tests/unit/core-reset-baseline.test.ts
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Madar builds a local graph for a JavaScript or TypeScript repository. Its MCP se
retrieve(question, budget?)
```

The result is a small set of exact source excerpts and directed relationships, or an explicit boundary explaining why evidence could not be returned. There are no tool profiles or alternate retrieval modes to choose.
The result is a complete, ordered answer dossier backed by exact source evidence, or an exact non-ready state naming what could not be proven. There are no tool profiles, fallback searches, or alternate retrieval modes to choose.

MCP advertises only the tools capability. It exposes no resources or prompts.

Expand Down Expand Up @@ -60,9 +60,9 @@ madar query "what calls enqueueInvoice?" --budget 2000

## What the result means

Results contain authenticated nodes and excerpts, directed relationships, explicit boundaries, and size metrics. `evidence` means the returned path is usable; other outcomes name the focused verification needed instead of implying a path Madar did not prove.
`ready` contains a non-truncated dossier: the normalized query, proven obligations, roots and terminals, direct or channel links, partial-order groups, and SHA-256-authenticated files, excerpts, controls, entities, and proofs. `incomplete`, `unsupported`, `stale`, `unavailable`, and `corrupt` name the exact condition instead of implying a path Madar did not prove.

Results include at most 12 files, 25 snippets, one directional closure pass, and 4,000 serialized tokens. See [MCP response shape](https://github.com/mohanagy/madar/blob/next/docs/mcp-response-shape.md) for the exact envelope.
Results include at most 12 files, 25 authenticated excerpts, two bounded recovery passes, and 4,000 serialized tokens. See [MCP response shape](https://github.com/mohanagy/madar/blob/next/docs/mcp-response-shape.md) for the exact envelope.

## How it works

Expand All @@ -76,7 +76,7 @@ JavaScript / TypeScript repository
retrieve(question, budget?)
|
v
exact excerpts + directed relationships
ordered claims + authenticated proof
```

`madar generate .` uses one canonical compiler-backed path for `.js`, `.jsx`, `.ts`, and `.tsx`. Other source languages and non-code formats produce no graph facts and are reported as unsupported when they matter to a question.
Expand Down
8 changes: 4 additions & 4 deletions docs/agent-governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
Madar governance is intentionally small:

1. call `retrieve` once for a repository question, preserving the user's question
2. use only authenticated nodes, exact excerpts, and directed relationships as Madar evidence
3. state every returned evidence boundary
4. make focused source reads only where the result cannot carry the task
5. never convert a partial or unsupported path into a complete claim
2. use a `ready` dossier's obligations, flow, and authenticated evidence as Madar evidence
3. state every exact non-ready `missing`, `reason`, or `failure`
4. make focused source reads only for the named gap
5. never convert a non-ready result into a complete claim

The same rules apply to `madar query`, which is the CLI transport for the same retrieval contract.

Expand Down
15 changes: 8 additions & 7 deletions docs/concepts/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ source scan

```text
question
-> lexical graph anchors
-> one bounded directional closure
-> locate, explain, or workflow obligation plan
-> bounded graph-coherent corridor selection
-> at most two structural/evidence recovery passes
-> source hash and range authentication
-> deterministic bounded slice
-> evidence or explicit boundary
-> atomic required-claim and proof packing
-> ready dossier or exact non-ready state
```

The retrieval pipeline has no profile, planner, recovery engine, semantic reranker, session state, or task-specific product wrapper.
The retrieval pipeline has one deterministic planner, workflow builder, and evidence hydrator. It has no profile, LLM reranker, fallback search, second retrieval engine, session state, or task-specific product wrapper.

Its hard output limits are 12 files, 25 snippets, one closure pass, and 4,000 serialized tokens.
Its hard output limits are 12 files, 25 authenticated excerpts, three roots, 32 initial candidates, 512 explored nodes, 24 causal hops, two recovery passes sharing 64 total recovery-frontier nodes, three alternate seeds per missing obligation, and 4,000 serialized tokens.

CLI `query`, direct application use, and MCP `retrieve` serialize byte-identical results for the same accepted graph and normalized request. MCP advertises only the tools capability, exactly one tool, and no resources or prompts.

Expand All @@ -42,4 +43,4 @@ An excerpt is evidence only when:
- current file bytes match the canonical SHA-256 hash
- the graph line range exists exactly in those bytes

Failures become missing, unsupported, stale, unavailable, corrupt, disconnected, or truncated boundaries. They are never converted into confidence scores.
Missing proof or a selection/budget limit becomes `incomplete`; unsupported intent/source, stale bytes, unavailable source, and corrupt facts retain their exact states. A non-ready response never exposes a partial dossier as answer-ready evidence or converts a gap into a confidence score.
Loading
Loading