feat(ingestion): place raw notes in a wing without human typing (#205 S1–S4, S6) #207
Open
mathias
wants to merge 15 commits from
wt/wing-layout into main
pull from: wt/wing-layout
merge into: :main
:main
:wt/wing-layout
:wt/inbox-zero
:fix/pipeline-writes-158
:fix/edit-path-io-192
:fix/create-regular-188
:fix/guard-special-179
:fix/linkcheck-keeps-write-178
:fix/write-dir-redirect-173
:fix/tunnel-detect-174
:fix/pipeline-root-157
:w3/154-mcp-auth-wiring
:fix/tunnel-traversal
:test/in-brain-targets-171
:fix/brain-scan-root-157
:fix/graph-wiki-root-157
:fix/vectorstore-root-157
:fix/notes-root-157
:fix/search-root-157
:fix/brainwide-root-157
:fix/raw-symlinks-153
:feat/symlink-tripwire-157
:fix/rest-auth-149
:fix/123-brain-edit-envelope-check
:feat/106-page-aware-extraction
:fix/105-source-drift-on-main
:fix/104-surface-merge-conflicts-on-main
:agent/b1-compile-on-ingest
:agent/104-surface-merge-conflicts
:agent/105-source-drift
:agent/107-loud-unconfigured-alerting
:feat/brain-read-edit
:fix/capture-i1-refusal-names-target
:renovate/configure
:feat/consolidate-single-harness
:icebox/cmd-routing
:feat/brain-pending-promote
:fix/close-session-classification-refresh
:fix/capture-summarywriter
No Reviewers
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#207
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Refs #205 (steps 1–4 and 6; step 5 migration is a separate PR). Risk: medium — changes where ingestion writes and adds an automatic decision path. Off by default: nothing changes until
INGEST_WING_RESOLVE=1is set on the ingestion deployment.What
internal/wing:Resolver— explicitraw/<wing>/subfolder orwing:frontmatter wins; otherwise a local-LLM classifier and an independent BM25 scorer must agree above a threshold on a wing that is known and eligible (classification.yaml: only wings classifying below Confidential; an untagged wing fails safe to confidential). Never creates a wing; never auto-assigns a client/untagged wing; anything else is an Exception.pipeline.Config.Wing: writeswiki/<wing>/sources/andwiki/<wing>/facts/(concept/entity told apart bykind:), withwing/hallfrontmatter. Inventory, title resolution and back-references are confined to the target wing. Empty Wing = retired layout, unchanged.raw/needs-review/with the proposal in frontmatter; never extracted.needs-review/is skipped by the walk.brain_pendinglists parked notes withqueue,proposed_wing,proposal_reason,proposal_confidence. Approve = move the note intoraw/<wing>/.cmd/wingbacktest: measures the policy against pages whose wing a human already chose.Evidence
go vet,go test -race ./...(0 FAIL),golangci-lint0 issues.needs-review/skipped, exception never ingested, frontmatter forgery via model text (%q), re-synced unchanged note not re-extracted.Known limits / not in this PR
hyperguildandhomelabare Internal inclassification.yamltoday, so with the flag on only those wings can be auto-assigned. Tagging more wings is a sovereignty decision for Mathias (the file says so).Knownwings are read at startup; a newly created wing is unknown until restart (fails safe: parked).Review ask
Verify by execution: (1) no path writes into an ineligible or unknown wing, including via the model's answer, an explicit folder name, or
../-style names; (2) a model-supplied string cannot forge a frontmatter key in a parked note; (3) with the flag off the watcher behaves as before; (4) cross-wing isolation of inventory/back-references.🤖 Generated with Claude Code
Created via git-mcp on behalf of @mathias
Independent review pass (pr-gatekeeper agent, verified by execution; reviewer is an agent, not four-eyes). First verdict: BLOCK. Resolution:
Confirmed and fixed (each has a test that failed first and a mutant that fails it):
-2,-3suffixes.TestProcessDir_SameNamedNotesAreAllParkedNoneOverwritten.proposed_wing(model reply or folder name with newline), and a note's ownproposal_*keys outranking the resolver's (first-wins reads).proposed_wingis emitted only for a plain wing name (else it is quoted into the reason); the note's ownproposal_*keys are dropped.TestProcessDir_AHostileWingCannotForgeTheProposal,..._ANotesOwnProposalKeysDoNotOutrankTheResolvers.wing:is honoured; one valid frontmatter block on parking.Acknowledged, not changed (stated so the reviewer's finding is not silently dropped):
raw/needs-review/is skipped by the walk unconditionally, andbrain_pendinglists it regardless of the flag. Deliberate: a note parked while the flag was on must not be ingested into the retired layout if the flag is later turned off. Nothing else differs withResolver == nil.stuckskip set is in memory, so after a restart a note whose original cannot be removed is parked once more (production initialises it inStart; only directprocessDiruse with a nil set re-parks every poll).INGEST_LLM_URL, which is the local model in the deployment (koala/qwen35-9b-fast, sovereignty: strict). Not enforced in code.My own slip, disclosed: the fix commit was pushed with
internal/extract's raw-reader guard red because I chained the gate with;. The next commit addsreserveParkedNameto that guard's own allowlist (it creates a file in needs-review/, reads nothing from raw/). Please look at that allowlist change specifically. Full gate now:go vet ./...andgo test -race ./...exit 0, 0 FAIL lines, golangci-lint 0 issues.Also on this branch since the review:
internal/migrate+cmd/wingmigrate(#205 S5, dry-run by default,-applyonly performs Auto items;-what-if-eligibleis dry-run only). Not yet run against the live brain; the plan needs the backtest and Mathias'sclassification.yamltags first.Created via git-mcp on behalf of @mathias
Independent review pass 3 (pr-gatekeeper agent, by execution; agent, not four-eyes): nothing blocks at
bd33a0a.Fixed and re-probed by the reviewer:
n-2.md; 5 polls, no duplicate storm, no loss.raw/needs-reviewas a symlink (outside the brain, and towiki/client-x), as a regular file: refused every poll, nothing written through it, note stays in raw/. Missing: created as a real directory. Symlinkedraw/itself: refused.----,---x, EOF-without-newline fences handled; CRLF normalised; a note's ownproposed_wingdropped.go vet ./...andgo test -race -count=1 ./...exit 0, 0 FAIL.Residual, accepted and documented:
changedSinceis size+mtime (a same-size rewrite with a restored mtime is not detected; the new bytes then survive only in the parked copy);appendWatcherLog, theretiremarker fallback andcopyFromRootare path-based but pre-existing and not the park path; a planted needs-review symlink produces a persistent error log (loud, intended).Not verified by the reviewer: PDF parking through these paths, the TOCTOU between
changedSinceand Remove, real Syncthing behaviour.The guard allowlist entry from
1e14c56has been reverted (parking no longer opens files by path).Created via git-mcp on behalf of @mathias
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.