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
37 changes: 14 additions & 23 deletions PROJECT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# FlyBot — Project State


#2378 reuses role Apps for bounded PR/CI reads and hash-verified host logs in Reviewer scratch; absence or incomplete pages are not verification success.


#2375 adds explicit exit-proven claim retirement through existing pools/Effects; preserve drafts, pending results and unknown writes. No cancellation or automatic routing.

#2377 makes successor baseline choice explicit and durable; preserve cancelled history, Task-role context and draft lineage without inventing PR or test evidence.
Expand Down Expand Up @@ -221,28 +224,16 @@ cutover identities, unknown-exit barriers and private-context publication approv
Original host/service deployment and real-provider acceptance remain separate.
See canonical Task, storage, service, compatibility and deployment documentation.

### Remaining backend convergence — #2292

Task execution pins its associated repository independently of Project defaults; local repository-free decision Tasks preserve normal Sessions/handoff/Chief acceptance without inventing code/tests. Actual Codex validation exposed malformed Chief requirement output: types are now explicit, failed admission is visibly blocked and valid retry retains history. Release Tasks now separate explicit human/watch publication authority, durable external observation and Chief closure; unknown results reconcile without repeated publication. Attempt logs and legacy agent history now return bounded hash-bound pages with lossless source references. Initial execution pins an approved active logical role/capability, catalog revision and Task requirements revision. Default developer remains compatible; a unique custom developer can replace it, while ambiguity needs an explicit target. Chief start Effects, capacity queues and Attempts retain the choice. Catalog/requirements changes block pending work for explicit recovery; active Session conventions remain frozen.

#2334 follow-up (updated issue): shell 127 / missing test runners are environment
blockers. testsStarted records command invocation; stage distinguishes runner startup
from preparation and tests. Nested Node test markers do not enter host verification.

#2338 preflights input receipts before host side effects and returns correctable
errors to the original role/Chief Session. Frozen proposals, bounded tool-free
correction and durable receipts preserve work; stale/revoked input stays denied.

#2347 recovers birth-proven unmanaged native Chief invocations, preserves unknown
external execution, and admits explicit sealed-candidate verification continuation
under current authority/context gates. No live rollout or unknown-exit override.
### Current delivery guarantees

#2331 permits a valid current-SHA Reviewer approval to enter an explicitly scoped
host merge Effect. Current branch/check/identity/exit gates remain; actual merge
is separate from Chief Task completion, signed forge review, release and deployment.
Tasks pin their associated repository and approved role/requirements revisions;
repository-free decisions do not invent code or tests. Release publication needs
explicit authority, observed Effects and Chief closure. Large histories use bounded
hash-bound pages. Missing runner/dependencies are environment failures, not assertion
failures. Receipts use same-context bounded correction and never acknowledge stale
inputs. Recovery preserves exact exit proof, candidate/Session/history and permits
evidence-supported verification continuation. Reviewer approve, host merge, signed
forge review, release/deployment and Chief acceptance remain distinct. Operations
consultation uses scoped personal agents; watch grants no host privileges.

#2342 keeps autonomous progress as Chief guidance under configurable action/time
budgets. Explicit human-selected operations contacts receive only scoped Project
assistance; replies bind the original question, requirements, generation and
candidate. Original inbox/Effects and schedules handle notification compensation;
watch never grants host privileges. See [assistance](docs/chief-assistance.md).
Detailed dated implementation checkpoints are [preserved verbatim](docs/history/2026-11-04-reliability-memory-checkpoints.md); current contracts remain in the owned subsystem docs.
33 changes: 9 additions & 24 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# FlyBot — Roadmap


#2378: enable explicit Actions reads/download hosts and retrievable exact-SHA test artifacts; real installed-App/private-repository acceptance remains separate.


#2375: retire proven exited responsibility before fresh integration admission; source/isolated tests remain separate from live recovery.

#2377: continue the verified original seed or explicitly integrate a new baseline; no silent draft reset or cancelled Attempt revival.
Expand Down Expand Up @@ -178,27 +181,9 @@ exit retains capacity. Real-provider parallel/restart/migration/UI and Release
operational acceptance remain distinct pending evidence under #2292. Previously
deferred multi-repository automation is not reactivated by this recovery work.

Updated #2334 acceptance adds actual npm missing-vitest/127 classification and
clean/prepare/same-candidate retest coverage; it remains a source/test follow-up,
not live npm ci provisioning or production Vitest acceptance.

#2338: bounded input receipt correction in the original Task-role/Chief context,
with all-plan preflight, immutable business evidence and explicit incomplete
recovery. Tests distinguish deterministic role transport and actual SDK execution
with a local provider fixture from paid-model/live project acceptance.

#2347 adds bounded inspection/stop reconciliation, explicit recovery facts and
same-Attempt Tester/Reviewer continuation. Real disposable unmanaged DSH restart
reaches a later original scheduled inspection with deterministic inference; paid
model/live-instance acceptance stays separate. Legacy missing namespace evidence
requires exact operator corroboration, never a guessed worker release.

#2331: authorized host merge follows Reviewer approval through original broker/
Effects, with Draft ready, configured CI/review/queue and unknown-write recovery.
Runtime merge is opt-in per repository/base; contributor delivery still follows
Owner-authorized CI/merge scope and does not authorize a deployment.

#2342: scoped operations consultation, original-message notification reminders,
configurable compensation schedules and current-state reply fences. Disposable
DSH/HTTP acceptance separates deterministic Chief/native/external protocol evidence
from paid-model judgement, actual host repair and production rollout.
Current reliability guarantees: environment attribution is separate from code
verification; receipt correction retains context and evidence; recovery retains
exact exit and scope fences; configured host merge is separate from Chief closure;
operations replies are input, not implicit recovery. Historical acceptance notes
are [preserved verbatim](docs/history/2026-11-04-reliability-memory-checkpoints.md).
Real provider/App/SDK, deployment and UI acceptance remain separate from fixtures.
1 change: 1 addition & 0 deletions changelog.d/2378.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose scoped Reviewer Actions access, explicit paged GitHub evidence and hash-verified host-test logs in resource scratch without changing candidate or acceptance boundaries.
3 changes: 2 additions & 1 deletion core/src/host/project-codex-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function cli(executable, args, { env, cwd, signal, rpc = false }) {
})
}

export const roleGithubDomains = scope => scope ? Object.fromEntries(['github.com', 'api.github.com', 'objects.githubusercontent.com', 'codeload.github.com', ...(scope.downloadHosts ?? [])].map(host => [host, 'allow'])) : {}
export async function validateCodexConfiguration(request, options, { executable, pathDirectory } = codexExecutable()) {
const declared = options.configOverrides.find(value => value.startsWith('permissions.flybot-role='))
if (!request.noTools && (!declared || /":tmp"\s*=/.test(declared))) throw failure('permission-profile-mismatch')
Expand All @@ -92,7 +93,7 @@ export async function validateCodexConfiguration(request, options, { executable,
// layers establish a conflict. Never log their values or credential material.
if (!Array.isArray(resolved.layers) || resolved.layers.some(layer => Object.hasOwn(layer.config ?? {}, 'sandbox_mode') || Object.hasOwn(layer.config ?? {}, 'sandbox_workspace_write')) || resolved.config?.default_permissions !== 'flybot-role') throw failure('legacy-sandbox-conflict')
const profile = resolved.config?.permissions?.['flybot-role']
if (profile?.extends !== (request.readOnly ? ':read-only' : ':workspace') || profile.filesystem?.[':tmpdir'] !== 'write' || Object.keys(profile.filesystem ?? {}).some(key => key.startsWith(':') && key !== ':tmpdir') || profile.network?.mode !== 'limited' || !sameRules(profile.network?.domains, request.roleGithub ? { 'github.com': 'allow', 'api.github.com': 'allow' } : {}) || !sameRules(profile.network?.unix_sockets, {}) || resolved.config?.features?.network_proxy !== true) throw failure('permission-profile-mismatch')
if (profile?.extends !== (request.readOnly ? ':read-only' : ':workspace') || profile.filesystem?.[':tmpdir'] !== 'write' || Object.keys(profile.filesystem ?? {}).some(key => key.startsWith(':') && key !== ':tmpdir') || profile.network?.mode !== 'limited' || !sameRules(profile.network?.domains, roleGithubDomains(request.roleGithub)) || !sameRules(profile.network?.unix_sockets, {}) || resolved.config?.features?.network_proxy !== true) throw failure('permission-profile-mismatch')
}
return { codexPathOverride: executable, env, capability: { kind: 'codex-cli-config/v1', version: '0.166.0', profile: request.noTools ? 'chief-read-only' : 'flybot-role', modelInvoked: false } }
}
2 changes: 1 addition & 1 deletion core/src/host/project-delivery.js
Original file line number Diff line number Diff line change
Expand Up @@ -1112,7 +1112,7 @@ export function createProjectDelivery(rt, { publisher, releaseProvider, roleArti
else await workspaceGit(workspace, ['reset', '--hard', attempt.candidateSha])
}
if (project.repository) { await prepareWorkspace(attempt, workspace, settings); await prepareIntegrations(attempt, workspace) }
context = await buildProjectContext(rt, { retrieval: true, policy, projectId: project.id, taskId: task.id, role, revision: pinned.revision, resourceId: lease?.resourceId, definition, memory: definition.memory, knowledge: ownEntry(state.projectKnowledge ?? {}, project.id) ?? [], requirement: attempt.requirement, feedback: attempt.feedback, projectFacts: { roleGithub: roleGithubStatus(rt, project), mergePolicy: projectMergePolicyStatus(settingsOf(project), project.defaultBranch ?? 'main'), environmentPreparation: workspace.environmentPreparation ? { id: workspace.environmentPreparation.id, status: workspace.environmentPreparation.status, executor: workspace.environmentPreparation.executor, identitySha256: workspace.environmentPreparation.identitySha256 ?? null, sha256: workspace.environmentPreparation.sha256 } : null, workspaceMaterialization: workspace.materialization ?? null, ...(pinned.change ? { roleDefinitionChange: pinned.change } : {}), integrationInputs: structuredClone(attempt.integrationInputs ?? []), testFailure: await failureContext(attempt), publicationBinding: attempt.publicationBinding ?? null, integrationInstruction: attempt.publicationBinding ? 'Merge the pinned upstreamSha into the published candidate using --no-commit, resolve and stage conflicts before handoff. Host sealing retains both trusted inputs; never fabricate parents or discard backend changes.' : null, workingDraft: !attempt.candidateSha && workspaceFor(attempt, 'developer') ? { kind: 'unverified-workspace-reference', resourceId: workspaceFor(attempt, 'developer').resourceId, path: workspaceFor(attempt, 'developer').resourceId === workspace.resourceId ? workspaceFor(attempt, 'developer').path : null, note: 'Unsealed source, not candidate or verification evidence. Cross-resource drafts require a sealed artifact before verification.' } : null, handoff: task.collaboration?.history.at(-1) ? { purpose: task.collaboration.history.at(-1).purpose, reason: task.collaboration.history.at(-1).reason } : null, candidateSha: attempt.candidateSha ?? null, tests: attempt.evidence.tests ? { sha: attempt.evidence.tests.sha, passed: attempt.evidence.tests.passed, status: attempt.evidence.tests.status ?? null, stage: attempt.evidence.tests.stage ?? null, reason: attempt.evidence.tests.reason ?? null, sha256: attempt.evidence.tests.sha256, command: attempt.evidence.tests.command } : null, review: attempt.evidence.review ? { sha: attempt.evidence.review.sha, verdict: attempt.evidence.review.verdict, summary: attempt.evidence.review.summary } : null }, repository: await repositoryContext(project.id, attempt.baseSha, project) })
context = await buildProjectContext(rt, { retrieval: true, policy, projectId: project.id, taskId: task.id, role, revision: pinned.revision, resourceId: lease?.resourceId, definition, memory: definition.memory, knowledge: ownEntry(state.projectKnowledge ?? {}, project.id) ?? [], requirement: attempt.requirement, feedback: attempt.feedback, projectFacts: { roleGithub: roleGithubStatus(rt, project), mergePolicy: projectMergePolicyStatus(settingsOf(project), project.defaultBranch ?? 'main'), environmentPreparation: workspace.environmentPreparation ? { id: workspace.environmentPreparation.id, status: workspace.environmentPreparation.status, executor: workspace.environmentPreparation.executor, identitySha256: workspace.environmentPreparation.identitySha256 ?? null, sha256: workspace.environmentPreparation.sha256 } : null, workspaceMaterialization: workspace.materialization ?? null, ...(pinned.change ? { roleDefinitionChange: pinned.change } : {}), integrationInputs: structuredClone(attempt.integrationInputs ?? []), testFailure: await failureContext(attempt), publicationBinding: attempt.publicationBinding ?? null, integrationInstruction: attempt.publicationBinding ? 'Merge the pinned upstreamSha into the published candidate using --no-commit, resolve and stage conflicts before handoff. Host sealing retains both trusted inputs; never fabricate parents or discard backend changes.' : null, workingDraft: !attempt.candidateSha && workspaceFor(attempt, 'developer') ? { kind: 'unverified-workspace-reference', resourceId: workspaceFor(attempt, 'developer').resourceId, path: workspaceFor(attempt, 'developer').resourceId === workspace.resourceId ? workspaceFor(attempt, 'developer').path : null, note: 'Unsealed source, not candidate or verification evidence. Cross-resource drafts require a sealed artifact before verification.' } : null, handoff: task.collaboration?.history.at(-1) ? { purpose: task.collaboration.history.at(-1).purpose, reason: task.collaboration.history.at(-1).reason } : null, candidateSha: attempt.candidateSha ?? null, tests: attempt.evidence.tests ? { sha: attempt.evidence.tests.sha, passed: attempt.evidence.tests.passed, status: attempt.evidence.tests.status ?? null, stage: attempt.evidence.tests.stage ?? null, reason: attempt.evidence.tests.reason ?? null, sha256: attempt.evidence.tests.sha256, command: attempt.evidence.tests.command, logSource: { query: 'attempt.log', attemptId: attempt.id, sha256: attempt.evidence.tests.sha256, nativePath: (definition.execution?.harness ?? 'flybot') === 'flybot' ? attempt.evidence.tests.log : null } } : null, review: attempt.evidence.review ? { sha: attempt.evidence.review.sha, verdict: attempt.evidence.review.verdict, summary: attempt.evidence.review.summary } : null }, repository: await repositoryContext(project.id, attempt.baseSha, project) })
} finally { if (preparation && !state.projectResourceLeases?.[lease.id]?.preparation?.exitUnknown) await rt.projectResources.endPreparation(preparation) }
} catch (error) {
const registered = lease && ownEntry(state.projectResourceLeases ?? {}, lease.id)
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/project-github-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function githubIdentityMetadata(identity) {
export function createGithubAppProvider({ request = fetch, now = Date.now, readKey = privateCredentialFile } = {}) {
const cache = new Map(), refreshing = new Map()
async function resolve(settings, { repository, permissions = { contents: 'read' } } = {}) {
if (!validId(settings.appId) || !validId(settings.installationId) || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository ?? '') || !Object.keys(permissions).length || Object.entries(permissions).some(([key, value]) => !['contents', 'pull_requests', 'issues', 'checks'].includes(key) || !['read', 'write'].includes(value))) throw denied('GitHub App installation scope is not configured')
if (!validId(settings.appId) || !validId(settings.installationId) || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository ?? '') || !Object.keys(permissions).length || Object.entries(permissions).some(([key, value]) => !['contents', 'pull_requests', 'issues', 'checks', 'actions'].includes(key) || !['read', 'write'].includes(value) || key === 'actions' && value !== 'read')) throw denied('GitHub App installation scope is not configured')
let key, material
try {
material = await readKey(settings.privateKeyFile)
Expand Down
Loading
Loading