Skip to content

Commit c3877a5

Browse files
V48 Gate 3 (specification-implementation): file-tree pickers for source path hints + protected IP exclusions
Source path hints and Protected IP exclusions on /deposits are no longer typed — they are picked from the repository FILE TREE fetched at the selected repository·branch·commit: - New VCS `tree` resource (`/api/vcs?resource=tree&owner&repo&path&ref`) backed by VCSService.getRepositoryContent (provider listFiles), with empty/mock payloads alongside the existing resources. - New VCSFileTreePicker: lazy per-directory loading, expand/collapse, file rows select their exact path, directory rows select their prefix (`dir/`), selected-path chips with × removal, square theme. - MUTUAL EXCLUSIVITY: each picker receives the other's selections as conflicting paths — conflicting rows are disabled and labeled ('Already a source path hint' / 'Already a protected IP exclusion'). - Page state becomes string arrays; the dispatch payload sends protectedIpExclusions as an array (route already accepts arrays or newline strings); concept-level withholding is directed to the Obfuscations field (caption + explainer copy updated). Tests: new vcsFileTreePicker suite (empty state, ref-scoped fetch, lazy directory loading, file/prefix selection, chip removal, conflict disabling); the deposit dispatch-with-exclusions test now picks 'secret-engine/' from the tree and asserts the array payload. Spec: presentation-laws section gains the path-inputs-are-file-tree- pickers law (fetched at the selected source package, mutually exclusive, arrays on the wire, concepts → Obfuscations). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 626179e commit c3877a5

8 files changed

Lines changed: 492 additions & 49 deletions

File tree

BITCODE_SPEC_V48_NOTES.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,17 @@ shared transactions/telemetry components) is governed by these laws:
611611
TERMINAL_REPOSITORY_PROVIDERS with live connection state as the item
612612
description; switching provider rewrites the provider param and clears
613613
repo/branch/commit).
614-
- **Examples are placeholders.** Form fields never prefill example values —
615-
source path hints ships empty with the examples as placeholder text.
614+
- **Examples are placeholders.** Form fields never prefill example values.
615+
- **Path inputs are file-tree pickers.** Source path hints and Protected IP
616+
exclusions are picked from the repository FILE TREE fetched at the
617+
selected repository·branch·commit (new VCS `tree` resource →
618+
`VCSService.getRepositoryContent`, lazy per-directory), never typed:
619+
a file selects its exact path, a directory selects its prefix (`dir/`).
620+
The two selections are MUTUALLY EXCLUSIVE — a path picked on one side is
621+
disabled on the other, labeled with why. The dispatch payload carries the
622+
exclusions as an array (the route accepts arrays or newline strings).
623+
Concept-level withholding belongs to the Obfuscations field, not the
624+
exclusion picker.
616625
- **Aside cards are non-collapsible.** The Earnings / Governance / Session
617626
cards are plain sections; only their inner disclosures (Opportunity
618627
roots, Authority blockers, Disclosure boundary, Deposit proof detail)

uapi/app/api/vcs/route.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const resourceSchema = z.enum([
1818
'branches',
1919
'commits',
2020
'issues',
21+
'tree',
2122
]);
2223
const providerSchema = z.enum(['github', 'gitlab', 'bitbucket']);
2324

@@ -51,6 +52,8 @@ function buildEmptyResourcePayload(resource: z.infer<typeof resourceSchema>) {
5152
return { commits: [] };
5253
case 'issues':
5354
return { issues: [] };
55+
case 'tree':
56+
return { items: [] };
5457
}
5558
}
5659

@@ -133,6 +136,13 @@ function buildMockPayload(resource: z.infer<typeof resourceSchema>, provider: VC
133136
},
134137
],
135138
};
139+
case 'tree':
140+
return {
141+
items: [
142+
{ path: 'src', type: 'tree', sha: 'mock-tree-src' },
143+
{ path: 'README.md', type: 'blob', sha: 'mock-blob-readme', size: 2048 },
144+
],
145+
};
136146
}
137147
}
138148

@@ -236,6 +246,19 @@ export async function GET(request: Request) {
236246
const commits = await vcsService.listCommits(connectionHandle, owner, repo, { branch });
237247
return NextResponse.json({ commits });
238248
}
249+
case 'tree': {
250+
if (!owner || !repo) return NextResponse.json(buildEmptyResourcePayload(resource));
251+
const treePath = url.searchParams.get('path') || '';
252+
const ref = url.searchParams.get('ref') || undefined;
253+
const items = await vcsService.getRepositoryContent(
254+
connectionHandle,
255+
owner,
256+
repo,
257+
treePath,
258+
ref,
259+
);
260+
return NextResponse.json({ items });
261+
}
239262
case 'issues': {
240263
if (!owner || !repo) return NextResponse.json(buildEmptyResourcePayload(resource));
241264
const [issues, pullRequests] = await Promise.all([

uapi/app/deposits/DepositPageClient.tsx

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import {
8181
DEPOSIT_SESSION_ROW_EXPLAINERS,
8282
} from "@/app/deposits/deposit-stat-explainers";
8383
import { TelemetryExplainerTrigger } from "@/components/base/bitcode/execution/TelemetryExplainerTrigger";
84+
import { VCSFileTreePicker } from "@/components/base/bitcode/vcs/VCSFileTreePicker";
8485
import type {
8586
DepositOptionReviewDecision,
8687
DepositOptionReviewDecisionState,
@@ -157,8 +158,10 @@ export default function DepositPageClient() {
157158
const [obfuscations, setObfuscations] = useState(
158159
"Note anything to obfuscate or withhold from the synthesized options: internal names, proprietary framing, or sensitive specifics the source-safe AssetPacks should avoid surfacing.",
159160
);
160-
const [sourcePathHintsText, setSourcePathHintsText] = useState("");
161-
const [protectedIpExclusionsText, setProtectedIpExclusionsText] = useState("");
161+
// Picked from the repository file tree (selected repo·branch·commit);
162+
// hints and exclusions are mutually exclusive path sets.
163+
const [sourcePathHints, setSourcePathHints] = useState<string[]>([]);
164+
const [protectedIpExclusions, setProtectedIpExclusions] = useState<string[]>([]);
162165
const [optionsRequested, setOptionsRequested] = useState(false);
163166
const [synthesisRunId, setSynthesisRunId] = useState<string | null>(null);
164167
// Whether the attached run is an option synthesis (its completed output
@@ -386,14 +389,6 @@ export default function DepositPageClient() {
386389
],
387390
);
388391

389-
const sourcePathHints = useMemo(
390-
() =>
391-
sourcePathHintsText
392-
.split(/\r?\n|,/u)
393-
.map((entry) => entry.trim())
394-
.filter(Boolean),
395-
[sourcePathHintsText],
396-
);
397392
const sourceCriticalitySignals = useMemo(
398393
() => [
399394
{
@@ -943,7 +938,7 @@ export default function DepositPageClient() {
943938
sourceBranch: repositoryContext?.selectedBranch || null,
944939
sourceCommit: repositoryContext?.selectedCommit || null,
945940
obfuscations: effectiveInstructions,
946-
protectedIpExclusions: protectedIpExclusionsText,
941+
protectedIpExclusions,
947942
demandContext: [
948943
...depositRouteInput.depositoryDemandSignals.map(
949944
(signal) => signal.label,
@@ -989,7 +984,7 @@ export default function DepositPageClient() {
989984
depositRouteInput.depositoryDemandSignals,
990985
depositRouteInput.existingDepositorySignals,
991986
depositRouteInput.readingDemandSignals,
992-
protectedIpExclusionsText,
987+
protectedIpExclusions,
993988
refreshLiveRuns,
994989
replaceDepositRouteTransaction,
995990
repositoryContext,
@@ -1515,7 +1510,7 @@ export default function DepositPageClient() {
15151510
sourceBranch: repositoryContext?.selectedBranch ?? null,
15161511
sourceCommit: repositoryContext?.selectedCommit ?? null,
15171512
obfuscations,
1518-
protectedIpExclusions: protectedIpExclusionsText,
1513+
protectedIpExclusions,
15191514
},
15201515
outputDetails: synthesisActivity.outputDetails,
15211516
events: synthesisEvents,
@@ -1578,47 +1573,68 @@ export default function DepositPageClient() {
15781573
className="mt-2 min-h-[8rem] w-full border border-white/10 bg-black/30 px-3 py-3 text-sm leading-6 text-neutral-100 outline-none transition focus:border-emerald-300/35"
15791574
/>
15801575
</div>
1576+
{/* File-tree pickers over the selected repository·branch·
1577+
commit. Hints and exclusions are MUTUALLY EXCLUSIVE — a
1578+
path picked on one side is disabled on the other.
1579+
Concept-level withholding belongs to Obfuscations above. */}
15811580
<div className="mt-4 grid gap-4 tablet:grid-cols-2">
15821581
<div className="block">
15831582
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.16em] text-neutral-500">
1584-
<label htmlFor="deposit-source-path-hints-input">Source path hints</label>
1583+
<span>Source path hints</span>
15851584
<span onClick={(event) => event.stopPropagation()}>
15861585
<BitcodeInlineExplainer explainer={DEPOSIT_SECTION_EXPLAINERS.sourcePathHints} triggerAriaLabel="More info about this field" />
15871586
</span>
15881587
</span>
1589-
<textarea
1590-
id="deposit-source-path-hints-input"
1591-
value={sourcePathHintsText}
1592-
onChange={(event) =>
1593-
setSourcePathHintsText(event.target.value)
1594-
}
1595-
placeholder={"e.g. uapi/app/terminal/TerminalDepositComposer.tsx\npackages/pipelines/asset-pack/src/depository-supply-index.ts"}
1596-
className="mt-2 min-h-[6rem] w-full border border-white/10 bg-black/30 px-3 py-3 font-mono text-xs leading-5 text-neutral-100 outline-none transition focus:border-emerald-300/35"
1597-
/>
1588+
<div className="mt-2">
1589+
<VCSFileTreePicker
1590+
aria-label="Source path hints file tree"
1591+
provider={repositoryContext?.provider ?? "github"}
1592+
repositoryFullName={
1593+
repositoryContext?.selectedRepository?.fullName ?? null
1594+
}
1595+
treeRef={
1596+
repositoryContext?.selectedCommit ||
1597+
repositoryContext?.selectedBranch ||
1598+
null
1599+
}
1600+
selectedPaths={sourcePathHints}
1601+
onChange={setSourcePathHints}
1602+
conflictingPaths={protectedIpExclusions}
1603+
conflictLabel="Already a protected IP exclusion"
1604+
/>
1605+
</div>
15981606
</div>
15991607
<div className="block">
16001608
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.16em] text-neutral-500">
1601-
<label htmlFor="deposit-protected-ip-exclusions-input">
1602-
Protected IP exclusions (one per line)
1603-
</label>
1609+
<span>Protected IP exclusions</span>
16041610
<span onClick={(event) => event.stopPropagation()}>
16051611
<BitcodeInlineExplainer explainer={DEPOSIT_SECTION_EXPLAINERS.protectedIpExclusions} triggerAriaLabel="More info about this field" />
16061612
</span>
16071613
</span>
1608-
<textarea
1609-
id="deposit-protected-ip-exclusions-input"
1610-
value={protectedIpExclusionsText}
1611-
onChange={(event) =>
1612-
setProtectedIpExclusionsText(event.target.value)
1613-
}
1614-
placeholder={"e.g. src/secret-engine/\ninternal pricing model"}
1615-
className="mt-2 min-h-[6rem] w-full border border-amber-300/15 bg-black/30 px-3 py-3 font-mono text-xs leading-5 text-neutral-100 outline-none transition focus:border-amber-300/40"
1616-
/>
1614+
<div className="mt-2">
1615+
<VCSFileTreePicker
1616+
aria-label="Protected IP exclusions file tree"
1617+
provider={repositoryContext?.provider ?? "github"}
1618+
repositoryFullName={
1619+
repositoryContext?.selectedRepository?.fullName ?? null
1620+
}
1621+
treeRef={
1622+
repositoryContext?.selectedCommit ||
1623+
repositoryContext?.selectedBranch ||
1624+
null
1625+
}
1626+
selectedPaths={protectedIpExclusions}
1627+
onChange={setProtectedIpExclusions}
1628+
conflictingPaths={sourcePathHints}
1629+
conflictLabel="Already a source path hint"
1630+
/>
1631+
</div>
16171632
<span className="mt-1 block text-xs leading-5 text-neutral-500">
1618-
Excluded paths and concepts never enter AssetPack knowledge
1619-
synthesis: they are removed from the source inventory before
1633+
Excluded paths never enter AssetPack knowledge synthesis:
1634+
they are removed from the source inventory before
16201635
measurement, and candidates that touch them are dropped
1621-
fail-closed.
1636+
fail-closed. Concept-level withholding belongs in
1637+
Obfuscations above.
16221638
</span>
16231639
</div>
16241640
</div>

uapi/app/deposits/deposit-explainers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export const DEPOSIT_SECTION_EXPLAINERS = {
129129
kicker: 'Option synthesis',
130130
title: 'Source path hints',
131131
summary:
132-
'Optional pointers (one per line or comma-separated) to paths or concepts worth flagging — used to estimate this deposit\'s criticality and cost, not to change what synthesis measures.',
132+
'Optional pointers picked from the repository file tree (at the selected branch and commit) to paths worth flagging — used to estimate this deposit\'s criticality and cost, not to change what synthesis measures.',
133133
detail:
134134
'Hints that look sensitive (mentioning secrets, credentials, wallets, auth, keys, payments, or settlement) raise a review warning and nudge the deposit\'s estimated development cost and expected settlement upward — a client-side heuristic, not a synthesis-time exclusion.',
135135
points: [
@@ -140,7 +140,7 @@ export const DEPOSIT_SECTION_EXPLAINERS = {
140140
}),
141141
protectedIpExclusions: buildExplainer({
142142
kicker: 'Option synthesis',
143-
title: 'Protected IP exclusions (one per line)',
143+
title: 'Protected IP exclusions',
144144
summary:
145145
'The hard, fail-closed boundary: paths and concepts listed here never enter AssetPack knowledge synthesis at all.',
146146
detail:

0 commit comments

Comments
 (0)