-
-
Notifications
You must be signed in to change notification settings - Fork 6
Fix agent-loop release-set authority in dogfood replays #116
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
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
8fbc309
ci: make agent-loop the release-set root authority
voku a2250b3
ci: let agent-loop own sibling dependency versions
voku 1f44bba
ci: bind replay evidence to resolved first-party versions
voku 3c3b14e
ci: verify resolved agent-loop release set
voku bbc3df4
ci: reuse release-set authority check for issue 101
voku a901a37
docs: separate release constraints from replay evidence
voku dda059a
ci: record resolved siblings instead of constraining them
voku bfafb74
ci: keep only direct replay toolchain authority
voku 6a242b7
ci: keep only direct issue 101 toolchain authority
voku 540aad7
docs: record resolved release set as evidence
voku 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
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,187 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| const AGENT_LOOP_PACKAGE = 'voku/agent-loop'; | ||
| const OWNED_AGENT_PACKAGES = [ | ||
| 'voku/agent-kanban', | ||
| 'voku/agent-learning', | ||
| 'voku/agent-map', | ||
| 'voku/agent-recall-compiler', | ||
| 'voku/agent-session', | ||
| ]; | ||
| const STALE_SIBLING_RELEASE_FIELDS = [ | ||
| 'agent_kanban_release', | ||
| 'agent_learning_release', | ||
| 'agent_map_release', | ||
| 'agent_recall_compiler_release', | ||
| 'agent_session_release', | ||
| ]; | ||
|
|
||
| if ($argc < 3 || $argc > 4) { | ||
| fwrite(STDERR, "Usage: php tools/agent-loop/verify-release-set.php <issue.json> <composer.json> [composer.lock]\n"); | ||
| exit(2); | ||
| } | ||
|
|
||
| try { | ||
| $issue = readJsonObject($argv[1]); | ||
| $composer = readJsonObject($argv[2]); | ||
| $toolchain = requireArray($issue, 'toolchain', $argv[1]); | ||
| $require = stringRequirements($composer['require'] ?? [], 'require', $argv[2]); | ||
| $requireDev = stringRequirements($composer['require-dev'] ?? [], 'require-dev', $argv[2]); | ||
| $rootRequirements = $require + $requireDev; | ||
|
|
||
| $expectedAgentLoop = requireString($toolchain, 'agent_loop_release', $argv[1]); | ||
| $actualAgentLoop = $rootRequirements[AGENT_LOOP_PACKAGE] ?? null; | ||
| if ($actualAgentLoop !== $expectedAgentLoop) { | ||
| throw new \RuntimeException(sprintf( | ||
| '%s must require %s %s; got %s.', | ||
| $argv[2], | ||
| AGENT_LOOP_PACKAGE, | ||
| $expectedAgentLoop, | ||
| $actualAgentLoop ?? '<missing>', | ||
| )); | ||
| } | ||
|
|
||
| foreach (OWNED_AGENT_PACKAGES as $package) { | ||
| if (isset($rootRequirements[$package])) { | ||
| throw new \RuntimeException(sprintf( | ||
| '%s must not constrain %s directly; %s owns the first-party release set.', | ||
| $argv[2], | ||
| $package, | ||
| AGENT_LOOP_PACKAGE, | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| foreach (STALE_SIBLING_RELEASE_FIELDS as $field) { | ||
| if (array_key_exists($field, $toolchain)) { | ||
| throw new \RuntimeException(sprintf( | ||
| '%s.toolchain.%s duplicates transitive release-set authority; record the resolved lock instead.', | ||
| $argv[1], | ||
| $field, | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| if ($argc === 4) { | ||
| $lock = readJsonObject($argv[3]); | ||
| $resolved = resolvedVersions($lock, $argv[3]); | ||
| $resolvedAgentLoop = $resolved[AGENT_LOOP_PACKAGE] ?? null; | ||
| if ($resolvedAgentLoop !== $expectedAgentLoop) { | ||
| throw new \RuntimeException(sprintf( | ||
| 'Resolved %s must be %s; got %s.', | ||
| AGENT_LOOP_PACKAGE, | ||
| $expectedAgentLoop, | ||
| $resolvedAgentLoop ?? '<missing>', | ||
| )); | ||
| } | ||
|
|
||
| $releaseSet = [AGENT_LOOP_PACKAGE => $resolvedAgentLoop]; | ||
| foreach (OWNED_AGENT_PACKAGES as $package) { | ||
| $version = $resolved[$package] ?? null; | ||
| if ($version === null) { | ||
| throw new \RuntimeException('Resolved release set is missing ' . $package . '.'); | ||
| } | ||
| $releaseSet[$package] = $version; | ||
| } | ||
| ksort($releaseSet, SORT_STRING); | ||
|
|
||
| fwrite(STDOUT, json_encode( | ||
| ['resolved_agent_release_set' => $releaseSet], | ||
| JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR, | ||
| ) . PHP_EOL); | ||
| } | ||
| } catch (Throwable $exception) { | ||
| fwrite(STDERR, $exception->getMessage() . "\n"); | ||
| exit(1); | ||
| } | ||
|
|
||
| /** @return array<string, mixed> */ | ||
| function readJsonObject(string $path): array | ||
| { | ||
| $content = file_get_contents($path); | ||
| if ($content === false) { | ||
| throw new \RuntimeException('Cannot read ' . $path . '.'); | ||
| } | ||
|
|
||
| $decoded = json_decode($content, true, 512, JSON_THROW_ON_ERROR); | ||
| if (!is_array($decoded)) { | ||
| throw new \RuntimeException($path . ' must contain a JSON object.'); | ||
| } | ||
|
|
||
| return $decoded; | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $data | ||
| * @return array<string, mixed> | ||
| */ | ||
| function requireArray(array $data, string $key, string $path): array | ||
| { | ||
| $value = $data[$key] ?? null; | ||
| if (!is_array($value)) { | ||
| throw new \RuntimeException(sprintf('%s.%s must be an object.', $path, $key)); | ||
| } | ||
|
|
||
| return $value; | ||
| } | ||
|
|
||
| /** @param array<string, mixed> $data */ | ||
| function requireString(array $data, string $key, string $path): string | ||
| { | ||
| $value = $data[$key] ?? null; | ||
| if (!is_string($value) || $value === '') { | ||
| throw new \RuntimeException(sprintf('%s.%s must be a non-empty string.', $path, $key)); | ||
| } | ||
|
|
||
| return $value; | ||
| } | ||
|
|
||
| /** | ||
| * @param mixed $requirements | ||
| * @return array<string, string> | ||
| */ | ||
| function stringRequirements(mixed $requirements, string $section, string $path): array | ||
| { | ||
| if (!is_array($requirements)) { | ||
| throw new \RuntimeException(sprintf('%s.%s must be an object.', $path, $section)); | ||
| } | ||
|
|
||
| $result = []; | ||
| foreach ($requirements as $package => $constraint) { | ||
| if (!is_string($package) || !is_string($constraint)) { | ||
| throw new \RuntimeException(sprintf('%s.%s must contain string package constraints.', $path, $section)); | ||
| } | ||
| $result[$package] = $constraint; | ||
| } | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $lock | ||
| * @return array<string, string> | ||
| */ | ||
| function resolvedVersions(array $lock, string $path): array | ||
| { | ||
| $resolved = []; | ||
| foreach (['packages', 'packages-dev'] as $section) { | ||
| $packages = $lock[$section] ?? []; | ||
| if (!is_array($packages)) { | ||
| throw new \RuntimeException(sprintf('%s.%s must be an array.', $path, $section)); | ||
| } | ||
| foreach ($packages as $package) { | ||
| if (!is_array($package)) { | ||
| continue; | ||
| } | ||
| $name = $package['name'] ?? null; | ||
| $version = $package['version'] ?? null; | ||
| if (is_string($name) && is_string($version)) { | ||
| $resolved[$name] = $version; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return $resolved; | ||
| } | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For Composer 2 root packages, how are duplicate package names inrequireandrequire-devhandled during dependency resolution?💡 Result:
In Composer 2, having the same package name in both the require and require-dev sections of your root composer.json file is not a supported or recommended practice [1]. When a package is listed in both sections, Composer merges the constraints from both [1]. During dependency resolution, the solver must find a single version of the package that satisfies both constraints simultaneously [1]. If the constraints are incompatible—for example, if they require different, non-overlapping version ranges—the dependency resolution will fail, preventing the installation or update of your dependencies [2][1]. Even if the constraints are technically compatible, listing a package in both sections can lead to unexpected behavior and is generally discouraged by the Composer maintainers [2][1]. The
composer validatecommand will often issue a warning if it detects a package required in both sections, noting that this can result in unpredictable outcomes [2]. To ensure stable dependency resolution, it is advised to pick one section for each package—either require (for production and development) or require-dev (for development only)—and list it only once [1].Citations:
🏁 Script executed:
Repository: voku/Simple-PHP-Code-Parser
Length of output: 4924
🏁 Script executed:
Repository: voku/Simple-PHP-Code-Parser
Length of output: 5614
Reject duplicate
voku/agent-loopdeclarations before merging requirements.$require + $requireDevkeeps therequirevalue for duplicate keys. Composer evaluates both constraints, but this verifier validates only therequirevalue. Reject manifests that declarevoku/agent-loopin both sections before selecting its constraint.🤖 Prompt for AI Agents