add_task_scope is seeded through BuiltinToolSeeds, SeedTool inserts only
once, and ToolResolve serves the stored row, so existing installs kept the
old "this is the task's authorization boundary" text.
seedTaskScopeDescription runs from seedOrchestrationTools and updates the
description only:
UPDATE tools SET description=$new, updated_at=now()
WHERE key='add_task_scope' AND system AND description=$old
$old is the description as seeded at 0dc8bdf, kept verbatim; $new is the
code description from BuiltinToolSeeds. It is idempotent, a no-op when the
row is absent, and leaves an operator-edited description alone. It does not
use RefreshToolDefaults, which overwrites without condition.
TestTaskScopeDescriptionUpgrade (server, PG) covers the replacement, the
preserved custom text, an unchanged updated_at on a second run, and the
description ToolResolve serves to the planner.
SeedTool is first-insert only and ToolResolve serves the stored tools row,
so on an existing database the record_fact schema never gained the source
property: the model never saw the enum, every fact was stored as inference,
and prove_goal's target_response refusal could not fire.
seedFactSourceSchema copies properties.source from the record_fact seed into
the stored schema, only when the stored schema lacks it (same pattern as
seedFindingEvidenceSchema). Operator edits to the description and to other
properties are preserved, and a second run changes nothing. It runs from
seedOrchestrationTools next to the other schema seeders.
The PG test edits the stored row (no source, custom description and
parameter text), runs seedOrchestrationTools twice, and checks the added
enum, the preserved operator text, the unchanged row on the second run,
and that ToolResolve serves the source property to the worker.
Refs #124 (F4).
Add two optional free-text fields to findings so a reporter can record
what was tested and did not work (counterevidence) and why the
conclusion holds (confidence_rationale).
- db: idempotent ADD COLUMN IF NOT EXISTS, TEXT NOT NULL DEFAULT '',
no CHECK; RecordFindingInput, the INSERT, the shared select list and
all three scan sites carry them.
- agent: report_finding accepts both as optional strings; the worker
and pentest prompts say to fill them when confidence is not high.
- server: FindingDTO (list and detail) carries both, omitted when empty;
existing databases get the two schema properties added to the stored
report_finding row once, without overwriting user edits.
- web: the finding detail page shows each section only when non-empty.
Both fields stay optional: a missing field never refuses a finding.
Audit findings F5, F6 and F11 for issue #124.
F5, goal guards:
- goal_met refuses while any goal node of the task is open, and when the
goals cannot be read. A task without goal nodes keeps the old behavior.
- prove_goal accepts a fact as evidence only when its confidence is
"observed"; otherwise it refuses and the goal stays open. Findings are
still accepted.
F6, hint origin:
- add_hint stores the calling ToolSet's worker as the hint origin instead
of "human", and passes it to the planner trigger (TriggerEvent.HintOrigin).
- renderTriggers names the writer: the human via the main agent, a
cross-task orchestration, or an agent, with a neutral fallback.
F11, built-in rules for the planner and the main agent:
- Interceptor.MatchBuiltin evaluates the enabled "[built-in]" deny rules on
the intercept-enabled tools.
- The target-only guard applies them before the target gate, whether the
gate is on or off. User rules and the judge stay out of it.
- SetRulesForTest is a test seam for the store-less guard tests; Log is a
no-op without a store.
spawn_task now reads the target-gate scope of the calling session from the
tool context. When the caller is gated, the child task is created with the
gate on (D23) and with a copy of the calling task's block rules (D24). Pins
and allow rules are never copied, parent_ref is not consulted, and spawn_task
gets no new parameter. A gate-off caller, or a call with no scope, creates the
child exactly as before.
A conversation started by a trigger of a gated task is created with the gate
on (D25). A merged run is gated if any of its fires came from a gated task.
Pins are never copied. Fires of a gate-off task and fires with no task are
unchanged.
TestSpawnedTaskStartsWithGateOff becomes TestSpawnedTaskInheritsGateFromCaller.
New: TestSpawnTaskSeesCallerScopeThroughHarness and
TestTriggeredConversationInheritsGate.
The previous commit stops list_task_credentials from building its schema
with obj(map[string]any{}, ""). Two gaps remained:
- obj still emitted whatever it was given, so the same typo on another tool
would again produce "required": [""]. obj now drops names that are blank
after strings.TrimSpace and omits the "required" key when nothing is left.
- Tool schemas are cached in the tools table and SeedTool is insert-only, so
the code fix only reached fresh installs. Existing databases kept the
malformed row and strict providers kept rejecting every worker run.
healListTaskCredentialsSchema runs at startup next to the other tool
seeds and removes the "required" key from that one row, only when it is
exactly [""]:
UPDATE tools SET schema = schema - 'required', updated_at=now()
WHERE key='list_task_credentials' AND schema->'required' = '[""]'::jsonb
It is idempotent (a healed row no longer matches), needs no settings flag,
and leaves the description, bindings, enabled flag and every other tool
untouched. tool_schema_refresh_v7 is not bumped.
Tests: TestObjDropsBlankRequired and
TestListTaskCredentialsSchemaHasNoBlankRequired (agent),
TestHealListTaskCredentialsSchema (server, Postgres).
Add a machine-checked verification state to findings, separate from the
human disposition (status). A finding is verified only when a proof or
verification traffic body is bound, the agent declares POE L3 or higher,
and every evidence quote appears verbatim, after whitespace
normalization, in one of those response bodies.
- schema: poe_level, verification_status, verification_note on findings
(idempotent ADD COLUMN IF NOT EXISTS, no CHECK, no backfill).
- db: SetFindingVerification validates status and POE 0-4; the three
fields are scanned with the finding rows and exposed in the DTO.
- update_finding_report: optional poe_level and evidence_quotes. The
report is always saved; verification only decides the status. Read
failures give not_assessed, never unverified.
- report agent guidance: declare POE and exact quotes from the bound body.
Not in this change: UI badges and filters, the blocked and false_positive
transitions, and the stored default report prompt for existing databases
(the guidance is appended at run time).
A worker hitting a wall only a human can clear (captcha, email/SMS OTP, 2FA,
a decision) calls ask_operator with a precise question and an optional
screenshot. The question lands in the intent's transcript as a user-role turn
with an attachment card (the same {text, attachments} shape chat uploads use,
so the existing transcript UI renders it natively), and the intent pauses. The
operator replies through the existing message-to-worker flow; the reply resumes
the run with the answer as the next input. Images are whitelisted (png/jpg/
jpeg/webp/gif), capped at 10MB, and copied into the task uploads dir.
Closes#41
Every connect attempt (discovery, refresh, and the per-agent-run wiring) now
records its outcome on the server row: last_connect_at/ok/err, written through
a change-deduped update so per-run wiring does not amplify writes. The MCP page
shows a health chip per card (ok with time, or the failure reason), and a new
read-only mcp_status tool (bound to auto, worker and pentest) lets a worker
notice that a server it expected is down and say so instead of silently
degrading while the cached tool count keeps the card looking healthy.
Closes#38
Task credentials (#3): declare test accounts at task creation, and never
lose the accounts the agent registers on its own.
- task_credentials table (unique per task+domain+username; upsert
overwrites the password in place), idempotent schema migration
- POST /api/tasks accepts credentials; GET/POST/DELETE
/api/tasks/{id}/credentials serve the UI (manual add, e.g. relaying an
OTP-verified account; delete)
- empty password = the "agent generates" mode: the entry surfaces in the
worker's graph overview with an explicit instruction to register that
account with a strong password and record it
- new tools save_task_credential / list_task_credentials for worker and
pentest; the graph overview injects the task's credentials each round
with a standing note: reuse matching entries, always record accounts
you register
- New Task dialog: optional credentials section (domain, username,
password, note) with a dedicated-test-accounts hint
- task detail: Credentials tab, passwords masked with explicit
reveal/copy, agent-recorded badge, light polling so mid-run recordings
appear
One-shot binding migration (settings-flag-guarded, same pattern as the
other seeds) grants the two tools to worker+pentest on databases seeded
before this change; fresh DBs get them via WorkerTools()/the pentest
catalog.
Closes#3
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.
Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
full text search) now use ASCII equivalents; go build passes and the
test suite matches the pre translation baseline (remaining failures
need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
matching the backend, chat mentions tests pass
The manual v0.3.6 sync (3675f7e) was incomplete, and the follow-up merge
(fa7b3cd) resolved conflicts in favour of that copy, silently reverting three
shipped changes. main is already an ancestor of this branch, so re-merging
cannot bring them back; restore them explicitly.
- schema.sql: re-add the idempotent llm_profiles_format_check migration.
CREATE TABLE IF NOT EXISTS leaves an existing table alone, so databases
created before openai-responses kept the old two-value CHECK and rejected
every openai-responses profile.
- orchestration.go: re-add the reseedPlannerPrompt/reseedWorkerPrompt calls,
dropped by a comment-alignment hunk while the functions stayed behind as
dead code. Without them existing installs never receive the quantified
acceptance check or the negative-conclusion evidence threshold.
- CHANGELOG.md: restore the 0.3.6 entries, the @neouks credit and the correct
norma version range.
Also close three recovery gaps in the new cold-archive path:
- traffic: write the staging journal even when no legacy host directory
exists. Modern installs keep traffic in SQLite only, so a crash between the
PostgreSQL commit and the SQLite commit left the archived task's exclusive
traffic in the hot store with nothing for recovery to act on.
- recoverTaskArchiveStages: treat a missing journal.json as a discardable
directory instead of an error, which used to set recoveryFailed and stop the
archive worker from starting for the rest of the process lifetime.
- stageTaskArchiveFiles: replay the journal's renames when resuming an
existing staging root. The journal is written before the first rename, so
its presence did not guarantee a complete payload, and a resumed round could
package an archive missing transcripts or workspace files.