Repository navigation
Description
Is there an existing issue for this?
- I have searched the existing issues. ππ§βπ» - bug portable SDD catalog generation follows symlinks outside the repositoryΒ #771/π - Fix path handling in canonical SDK catalog generation nested links and output symlinksΒ #789 concern catalog publication/access; their current fix safely accesses CATALOG.md. This report concerns unregistered specification discovery, which remains reproducible on current develop after those fixes.
Which actions are affected?
Issue, Pull Request, Commits
Which platforms are affected?
macOS reproduced; filesystem Dirent handling is platform independent where symlinks are supported.
Description
The standalone SDK's discovery walk silently omits symlink entries under specs/. A regular unregistered Markdown SDD is rejected, but an unregistered Markdown symlink and a symlinked directory containing an SDD are both ignored. validateCatalog returns no errors and the actual standalone validation entrypoint prints PASS.
Expected: explicit unsupported-path/registration failure for these entries, consistently with the existing rejection of a registered symlink. This is a bounded catalog completeness/path-policy defect. It does not claim that discovery follows external links, reads secrets, executes symlinked code or overwrites targets.
Reproducing the issue
Use unmodified sources at develop ab5f7d7, src/infrastructure/specification_catalog_validator.cjs (Git blob 5caad166b383120e9c155c459f4e57019ab05297), with its original path-policy and publication dependencies (blobs 33c5478cf95323900af5030e4e8ac715e5a9f04b and 3391c7b331aa108acd21b44ec39ac0c2823946f4). No production source is changed.
Save this as repro.cjs and run node repro.cjs /absolute/path/to/copilot/src/infrastructure/specification_catalog_validator.cjs. All files and symlink targets are owned disposable fixtures and are removed in finally.
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const sdk = require(process.argv[2]);
const scratch = fs.mkdtempSync('/private/tmp/copilot-catalog-discovery-');
const root = path.join(scratch, 'repo');
const catalog = { version: 1, capabilities: [{
id: 'example', title: 'Example', status: 'proposed', scope: 'Discovery fixture',
owner: 'Maintainers', lastVerified: '2026-10-30', specs: ['specs/example.md'],
workflows: [], entrypoints: ['source/entry.py'], code: ['source/entry.py'],
tests: ['checks/example.py'], documentation: ['docs/guide.md'],
}] };
function write(relative, text = 'fixture\n') {
const target = path.join(root, relative);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, text);
}
try {
for (const p of ['specs/example.md', 'source/entry.py', 'checks/example.py', 'docs/guide.md']) write(p);
write('specs/catalog.json', JSON.stringify(catalog));
assert.deepEqual(sdk.validateCatalog(root, catalog), []);
sdk.main(['--write'], root);
write('specs/unregistered.md');
const regularErrors = sdk.validateCatalog(root, catalog);
assert.ok(regularErrors.length > 0);
fs.unlinkSync(path.join(root, 'specs/unregistered.md'));
const outside = path.join(scratch, 'owned-outside');
fs.mkdirSync(outside);
fs.writeFileSync(path.join(outside, 'hidden.md'), '# Unregistered specification\n');
fs.symlinkSync(path.join(outside, 'hidden.md'), path.join(root, 'specs/alias.md'));
const fileErrors = sdk.validateCatalog(root, catalog);
const registered = structuredClone(catalog);
registered.capabilities[0].specs.push('specs/alias.md');
const registeredErrors = sdk.validateCatalog(root, registered);
assert.ok(registeredErrors.length > 0);
fs.symlinkSync(outside, path.join(root, 'specs/alias-directory'), 'dir');
const combinedErrors = sdk.validateCatalog(root, catalog);
const discovered = sdk.discoverSpecificationFiles(root);
const savedExit = process.exitCode;
sdk.main([], root);
const validationExit = process.exitCode || 0;
process.exitCode = savedExit;
console.log(JSON.stringify({
source: 'vypdev/copilot develop ab5f7d78e2012e999bdf5a50903e879b20738e08',
regularUnregisteredErrors: regularErrors,
unregisteredFileSymlinkErrors: fileErrors,
unregisteredFileAndDirectorySymlinkErrors: combinedErrors,
registeredSymlinkErrors: registeredErrors,
discovered, standaloneValidationExit: validationExit,
}, null, 2));
} finally {
fs.rmSync(scratch, { recursive: true, force: true });
}copilot Version
Unreleased develop ab5f7d7, reproduced 2026-10-30 with Node 24 on macOS ARM64. Published v3 remains f5e5a4c / v3.10.6; this is not a claimed regression in a later published release. GeckoCV's copied SDK at 972023c has the same discovery omission, identified during review of vypdev/geckocv#32.
Relevant Log Output
specification catalog generation: PASS (1 capabilities)
specification catalog validation: PASS (1 capabilities)
regularUnregisteredErrors: ["specs/unregistered.md is not registered in the specification catalog."]
unregisteredFileSymlinkErrors: []
unregisteredFileAndDirectorySymlinkErrors: []
registeredSymlinkErrors: ["capabilities[0].specs[1] does not resolve to an existing file inside the repository without symlinks: specs/alias.md."]
discovered: ["specs/example.md"]
standaloneValidationExit: 0
Additional context and comments
Please define explicit fail-closed discovery handling for unsupported specification file/directory symlinks and add positive/negative tests, preserving containment and intentional non-spec exclusions. Do not follow symlinks to solve the omission. The normal unregistered-file rejection and registered-symlink rejection are independent controls already executed above.
Please handle the correction through the Copilot team's normal workflow. This reporter has not started an upstream implementation branch, changed the consumer SDK, reduced GeckoCV's 100-SDD scope or published/deployed anything. The separate read-only catalog output access finding in the older copied SDK should be addressed by adopting the already corrected upstream dependency set; it is not duplicated here.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
[-]SDD catalog discovery silently skips unregistered specification symlinks[/-][+]π - SDD catalog discovery silently skips unregistered specification symlinks[/+]vypbot commentedon Oct 30, 2026
Hi! Iβm @vypbot, the Copilot assistant for this repository.
I can answer questions, explain the codebase, propose implementation and test plans, review issues and pull requests for potential bugs or security problems, and help authorized maintainers apply changes.
Try
/copilot helpto see the available commands, or mention @vypbot with your question.efraespada commentedon Oct 30, 2026
Consumer verification update at Copilot SDK pin
071044e96e25d5b7866b2ff6ccbd11a53d12645f: unregistered file, directory and dangling specification links still disappear from direct SDK discovery. The consumer's three actual-package CLI regressions reproduce the omission (126 controls pass); this is the existing #804 condition, not evidence of a newer-release regression.GeckoCV PR #32, source
b00c85689e32456cedaa1d0d9697027e6d7d1e76, now has a pre-SDK specification-tree check. Its validation and generation commands reject those static links with no PASS, no rewritten catalog/registry and untouched targets. All SDK source/license bytes match the pinned upstream Git blobs; the consumer mitigation does not fix or close direct upstream discovery.Native verification 41096290736 passes all 129 metadata/architecture tests on a fresh GitHub-hosted runner. All 100 SDDs remain; no product scope or genuine gate is reduced. Exact SDK provenance, real CLI regression sources and the scoped audit are published in the PR. Upstream #804 remains the SDK owner's work; no upstream branch, implementation or release was created by this consumer update.
1 remaining item
vypbot commentedon Oct 30, 2026
Implementation plan
Acceptance: Automated tests demonstrate that unregistered Markdown file symlinks and symlinked directories under `specs/` produce validation errors without following their targets, while intentional exclusions and regular-file discovery behavior remain intact.
Need something else? Mention the bot with a question or use
/copilot help.vypbot commentedon Oct 30, 2026
Issue closed β historical progress
Current status: closed as completed.
The last estimate is retained below as history. Closure does not certify a new percentage, tests, or branch alignment.
Action required: none for this closed issue. If work continues, reopen the issue; a fresh assessment can update this same card.
Last assessment before closure (historical)
vypbot commentedon Oct 30, 2026
Issue closed: synchronization recommendation retired
No branch synchronization is required for this closed issue.
The previous recommendation is historical. Issue closure does not certify that the branches are synchronized.