Repository navigation
feat(orchestrator): combine adaptive routing with tenant cost attribution #496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
opencode-agent
merged 59 commits into
develop
from
feat/orchestrator-cost-attribution-hardened
Aug 18, 2026
Merged
Changes from 49 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
c3def46
fix(orchestrator): fail closed outside explicit development mode
seonghobae 6525970
test(orchestrator): prove production never returns deterministic fake…
seonghobae 02d1bf6
test(coverage): include orchestrator production boundary
seonghobae 2b02f23
docs(orchestrator): define fail-closed learned-coordination boundary
seonghobae eb9435e
ci(orchestrator): verify fail-closed production behavior once
seonghobae cc60a85
docs(changelog): record orchestrator production boundary
seonghobae bdc81fa
ci: remove completed orchestrator one-shot writer
seonghobae af69b5f
test(orchestrator): reject oversized provider responses
seonghobae 6281964
fix(orchestrator): bound provider response bodies
seonghobae 75f73e4
merge develop: preserve security hardening in orchestrator PR
seonghobae cc76f59
test(orchestrator): enforce streaming response byte budget
seonghobae 7ea77f0
fix(orchestrator): bound provider response while streaming
seonghobae c5c890e
docs(orchestrator): record streaming response bound
seonghobae 1e6916a
test(orchestrator): expect explicit development adapter
seonghobae 261cb5d
test(orchestrator): require hardened cost attribution
seonghobae 9ba9d6e
test(orchestrator): register attribution regression
seonghobae 6b51afa
test(api): bind AI cost attribution to project tenant
seonghobae 046e5ca
test(api): register tenant attribution regression
seonghobae b516e9b
test(orchestrator): bound attribution metadata
seonghobae b01ebbc
feat(orchestrator): forward bounded cost attribution
seonghobae 855e89c
feat(orchestrator): bind cost attribution to project tenant
seonghobae 4a806b5
test(orchestrator): cover residual production boundary branches
seonghobae d648c45
test(orchestrator): register residual branch coverage suite
seonghobae 766d577
test(orchestrator): cover IPv6 loopback transport
seonghobae 2a81fb4
fix(orchestrator): recognize IPv6 loopback hostname
seonghobae d072ce1
test(orchestrator): carry forward parent branch coverage
seonghobae 81ab288
test(orchestrator): register inherited coverage suite
seonghobae 12fc3ad
fix(orchestrator): carry forward IPv6 transport boundary
seonghobae e1d6c72
merge: refresh attribution stack on current orchestrator parent
seonghobae bd9bfe3
test(orchestrator): reject ambiguous provider endpoint configuration
seonghobae 268536f
fix(orchestrator): require a canonical provider origin
seonghobae 8c150a7
docs(orchestrator): document canonical provider origin
seonghobae 46723a0
test(orchestrator): classify provider rejection before response body
seonghobae 1578bf3
fix(orchestrator): classify provider rejection before body parsing
seonghobae b2f9639
test(api): isolate orchestrator mock from inherited provider config
seonghobae 759de58
chore(orchestrator): refresh attribution stack on hardened parent
seonghobae e482891
test(orchestrator): cover non-JSON response branch
seonghobae 7b94e5f
test(orchestrator): inherit parent coverage repair
seonghobae 650c7f6
chore(stack): record current orchestrator parent
seonghobae d57572a
test(orchestrator): require cancellation of rejected bodies
seonghobae dd85ee0
fix(orchestrator): cancel rejected provider response bodies
seonghobae c6a13c0
fix(orchestrator): preserve parent rejection cleanup in attribution s…
seonghobae ef8b807
test(orchestrator): preserve parent rejection cleanup regressions
seonghobae f86ff8d
merge(orchestrator): reconcile attribution stack with hardened parent
seonghobae dda097a
merge(orchestrator): reconcile attribution with protected develop
seonghobae 9f9ce0e
test(orchestrator): reject non-scalar attribution values
seonghobae 69b4439
fix(orchestrator): reject malformed attribution labels
seonghobae 59c109f
merge(orchestrator): reconcile protected XML import baseline
seonghobae b79b503
fix(stack): preserve protected toast tests in attribution branch
seonghobae c051470
merge: reconcile orchestrator attribution with current develop
seonghobae 0a03887
test(orchestrator): require prototype-free attribution map
seonghobae 4714670
fix(orchestrator): isolate attribution map prototype
seonghobae dc71cdf
test(orchestrator): require adaptive mode with hardened request
seonghobae 5510058
test(orchestrator): preserve attribution under adaptive mode
seonghobae bd88785
feat(orchestrator): compose adaptive routing with cost attribution
seonghobae 5a18f0b
docs(orchestrator): document adaptive attributed requests
seonghobae a728d87
docs(doctoring): trace adaptive attributed orchestration
seonghobae 3069978
docs(changelog): record adaptive attributed orchestration
seonghobae 36fa982
merge(develop): reconcile orchestration attribution with OpenCode config
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import assert from 'node:assert/strict'; | ||
|
|
||
| process.env.SCOPEWEAVE_DB = ':memory:'; | ||
| process.env.SCOPEWEAVE_JWT_SECRET = '0123456789abcdef0123456789abcdef'; | ||
| delete process.env.SCOPEWEAVE_DEV; | ||
| process.env.ORCHESTRATOR_URL = 'https://orchestrator.example'; | ||
| process.env.ORCHESTRATOR_TOKEN = 'secret-token'; | ||
| process.env.ORCHESTRATOR_MODEL = 'nvidia/nemotron-3-super-120b-a12b'; | ||
|
|
||
| const providerCalls = []; | ||
| globalThis.fetch = async (url, init) => { | ||
| providerCalls.push({ url: String(url), init }); | ||
| return new Response(JSON.stringify({ | ||
| choices: [{ message: { content: 'Grounded production response' } }], | ||
| }), { | ||
| status: 200, | ||
| headers: { 'content-type': 'application/json' }, | ||
| }); | ||
| }; | ||
|
|
||
| const { app } = await import(`../../server/app.mjs?attribution-api-test=${Date.now()}`); | ||
|
|
||
| const jsonRequest = (path, options = {}) => app.request(path, { | ||
| ...options, | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| ...(options.headers || {}), | ||
| }, | ||
| }); | ||
| const jsonBody = (value) => JSON.stringify(value); | ||
|
|
||
| async function createAccount(email) { | ||
| let response = await jsonRequest('/api/auth/signup', { | ||
| method: 'POST', | ||
| body: jsonBody({ email, password: 'password123', name: email }), | ||
| }); | ||
| assert.equal(response.status, 200, `${email} signup`); | ||
| const token = (await response.json()).token; | ||
| const auth = { authorization: `Bearer ${token}` }; | ||
| response = await jsonRequest('/api/me', { headers: auth }); | ||
| assert.equal(response.status, 200, `${email} account lookup`); | ||
| const account = await response.json(); | ||
| return { auth, orgId: account.orgs[0].id }; | ||
| } | ||
|
|
||
| const owner = await createAccount('orchestrator-owner@scopeweave.test'); | ||
| const outsider = await createAccount('orchestrator-outsider@scopeweave.test'); | ||
|
|
||
| let response = await jsonRequest('/api/projects', { | ||
| method: 'POST', | ||
| headers: owner.auth, | ||
| body: jsonBody({ name: 'Attribution Project' }), | ||
| }); | ||
| assert.equal(response.status, 200, 'owner creates attribution project'); | ||
| const projectId = (await response.json()).id; | ||
|
|
||
| response = await jsonRequest(`/api/projects/${projectId}/ai/brief`, { | ||
| method: 'POST', | ||
| headers: owner.auth, | ||
| body: jsonBody({ account: String(outsider.orgId), service: 'spoofed-client-service' }), | ||
| }); | ||
| assert.equal(response.status, 200, 'authorized owner receives AI briefing'); | ||
| assert.equal(providerCalls.length, 1, 'authorized briefing performs one provider call'); | ||
| assert.equal(providerCalls[0].url, 'https://orchestrator.example/v1/chat/completions'); | ||
| const providerBody = JSON.parse(providerCalls[0].init.body); | ||
| assert.deepEqual( | ||
| providerBody.attribution, | ||
| { service: 'scopeweave', account: String(owner.orgId) }, | ||
| 'the authenticated server-side project organization owns cost attribution', | ||
| ); | ||
| assert.notEqual( | ||
| providerBody.attribution.account, | ||
| String(outsider.orgId), | ||
| 'browser-supplied account data cannot spoof another tenant attribution', | ||
| ); | ||
|
|
||
| response = await jsonRequest(`/api/projects/${projectId}/ai/brief`, { | ||
| method: 'POST', | ||
| headers: outsider.auth, | ||
| body: jsonBody({ account: String(owner.orgId) }), | ||
| }); | ||
| assert.equal(response.status, 404, 'cross-tenant AI briefing hides project existence'); | ||
| assert.equal( | ||
| providerCalls.length, | ||
| 1, | ||
| 'cross-tenant requests are rejected before any contextual-orchestrator call', | ||
| ); | ||
|
|
||
| console.log('✓ AI briefing attribution tenant-boundary tests passed'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import assert from 'node:assert/strict'; | ||
|
|
||
| process.env.SCOPEWEAVE_DEV = ''; | ||
| process.env.ORCHESTRATOR_URL = 'https://orchestrator.example'; | ||
| process.env.ORCHESTRATOR_TOKEN = 'secret-token'; | ||
| process.env.ORCHESTRATOR_MODEL = 'nvidia/nemotron-3-super-120b-a12b'; | ||
|
|
||
| const calls = []; | ||
| globalThis.fetch = async (url, init) => { | ||
| calls.push({ url, init }); | ||
| return new Response(JSON.stringify({ | ||
| choices: [{ message: { content: 'Grounded production response' } }], | ||
| }), { | ||
| status: 200, | ||
| headers: { 'content-type': 'application/json' }, | ||
| }); | ||
| }; | ||
|
|
||
| const { chat } = await import( | ||
| `../../server/orchestrator.mjs?attribution-test=${Date.now()}-${Math.random()}` | ||
| ); | ||
|
|
||
| const messages = [{ role: 'user', content: 'status' }]; | ||
|
|
||
| assert.equal( | ||
| await chat(messages, { | ||
| service: 'scopeweave', | ||
| account: 42, | ||
| upstream_api: 'requested-upstream-label', | ||
| provider: 'requested-provider-label', | ||
| model_name: 'requested-model-label', | ||
| team: null, | ||
| group: '', | ||
| company: ' ', | ||
| unsupported_dimension: 'must-not-cross-boundary', | ||
| }), | ||
| 'Grounded production response', | ||
| ); | ||
|
|
||
| assert.equal(calls.length, 1); | ||
| const attributedBody = JSON.parse(calls[0].init.body); | ||
| assert.equal(attributedBody.model, 'nvidia/nemotron-3-super-120b-a12b'); | ||
| assert.equal(Object.hasOwn(attributedBody, 'provider'), false); | ||
| assert.deepEqual(attributedBody.attribution, { | ||
| service: 'scopeweave', | ||
| account: '42', | ||
| upstream_api: 'requested-upstream-label', | ||
| provider: 'requested-provider-label', | ||
| model_name: 'requested-model-label', | ||
| }); | ||
| assert.equal( | ||
| Object.hasOwn(attributedBody.attribution, 'unsupported_dimension'), | ||
| false, | ||
| 'unknown attribution keys never cross the ScopeWeave boundary', | ||
| ); | ||
|
|
||
| await chat(messages, { unsupported_dimension: 'x', account: ' ' }); | ||
| const emptyBody = JSON.parse(calls[1].init.body); | ||
| assert.equal( | ||
| Object.hasOwn(emptyBody, 'attribution'), | ||
| false, | ||
| 'an attribution field is omitted when no non-empty allowed dimensions remain', | ||
| ); | ||
|
|
||
| await chat(messages); | ||
| const legacyBody = JSON.parse(calls[2].init.body); | ||
| assert.deepEqual( | ||
| legacyBody, | ||
| { | ||
| model: 'nvidia/nemotron-3-super-120b-a12b', | ||
| messages, | ||
| }, | ||
| 'omitting attribution preserves the hardened request shape exactly', | ||
| ); | ||
|
|
||
| for (const invalidAttribution of [ | ||
| [], | ||
| 'scopeweave', | ||
| { service: 'x'.repeat(257) }, | ||
| { service: ['scopeweave'] }, | ||
| { account: { organization_id: 42 } }, | ||
| { team: Symbol('scopeweave') }, | ||
| { group: Number.NaN }, | ||
| { company: Number.POSITIVE_INFINITY }, | ||
| ]) { | ||
| await assert.rejects( | ||
| chat(messages, invalidAttribution), | ||
| (error) => error.code === 'orchestrator_attribution_invalid', | ||
| 'malformed, non-scalar, non-finite, or unbounded attribution fails before provider transport', | ||
| ); | ||
| } | ||
| assert.equal(calls.length, 3, 'invalid attribution never reaches the provider'); | ||
|
|
||
| console.log('✓ orchestrator attribution boundary tests passed'); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.