Skip to content
Draft
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
5 changes: 3 additions & 2 deletions cc-scheduler/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,8 +1306,9 @@ if !truth(&row["scheduler"]["key"]){let attempt=add_count(&row["scheduler"]["att
}
Err(error)
if error.kind() == std::io::ErrorKind::NotFound
&& ["cc-pc-tools-rs", "pc-capacity-audit", "localai-web-rs"]
.contains(&project) =>
&& (["cc-pc-tools-rs", "pc-capacity-audit", "localai-web-rs"]
.contains(&project)
|| (i["id"] == 2637 && project == "civ6-banseiki")) =>
{
!self.backend.repair_scope_allowed(path)
}
Expand Down
32 changes: 32 additions & 0 deletions cc-scheduler/tests/parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,38 @@ fn missing_private_registry_only_dispatches_verified_explicit_pc_repair() {
}
}
}

#[test]
fn new_owner_engineering_scope_only_admits_exact_item_with_verified_guard() {
for project in ["civ6-banseiki", "civ6", "other"] {
for id in [2636, 2637, 2638, 880] {
for allowed in [false, true] {
let mut f = Fixture::new();
fs::remove_file(f.e.root.parent().unwrap().join("client-projects.txt")).unwrap();
let mut db = f.e.todo().unwrap();
db["projects"][project] = json!(f.repo);
write_json(&f.e.todo_path, &db, 1).unwrap();
f.e.backend.repair_allowed = allowed;
f.put(item(id, project, "todo", &[]));
f.dispatch();
assert_eq!(
f.e.backend.launched.len(),
usize::from(allowed && id == 2637 && project == "civ6-banseiki")
);
}
}
}
// Presence of the original registry overrides the new finite admission.
let mut f = Fixture::new();
fs::write(f.e.root.parent().unwrap().join("client-projects.txt"), "civ6-banseiki").unwrap();
let mut db = f.e.todo().unwrap();
db["projects"]["civ6-banseiki"] = json!(f.repo);
write_json(&f.e.todo_path, &db, 1).unwrap();
f.e.backend.repair_allowed = true;
f.put(item(2637, "civ6-banseiki", "todo", &[]));
f.dispatch();
assert!(f.e.backend.launched.is_empty());
}
#[test]
fn repair_step_never_replays_legacy_questions_or_unknown_work() {
let mut f = Fixture::new();
Expand Down
27 changes: 27 additions & 0 deletions docs/next-work-2636.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 優先順位から選んだ次の実作業(ToDo2636)

2026-10-15の直接依頼による有限な工程であり、原保護名簿の復旧や元192項目の完了を表さない。元の停止・依存・履歴を変更しない。

00:44 JSTの共有スケジューラは稼働5、CPU平均18.5%、空きRAM45.90GiB、容量上の追加枠14を記録した。後のジョブメタデータ照合では稼働中・起動待ち6件、選定した製品の担当0件だった。非原子的な別時点の実測を同時点の数値として扱わない。

## 選定と限定範囲

受託の既存限定再開2527は未完依存2099/2274/2505を保持する。未知の受託資源は参照しない。次の万世紀では、元880が通常Prepareのstatus1/runtime installed=falseの原因修正を残している。この実害を直す新担当2637を正規ToDoへ指示書付きで登録した。Cargo/Civ6の割当を宣言し、資源割当前は起動しない。登録は実処理完了と区別する。

情報システム部の登録先localai-web-rsは実在するが、Gitの管理情報がなくorigin照合に失敗した。現在の共有ツール修復は2488/2628の実担当が進めており、同じ実装を追加しない。GoDD-MCP-Alphaは登録先の正確なoriginとGitHubの実所有者AutoDevJapanを確認したが、元1153は追加費用の本人判断待ちを残している。この新指示を支出承認として使わない。下位の工程や未知の自社サービスに範囲を広げない。

万世紀の登録先とGitHub APIの実所有者AutoDevJapanを照合した。正確なoriginは `git@github.com:AutoDevJapan/civ6-banseiki.git`。新担当2637の専用worktree、元880の保存作業先、同じ製品の登録先と3つの正確な指示書だけを限定検証する。別フォルダー、別番号のworktree、物理的に外へ脱出するリンクは許可しない。既存原保護名簿が存在するときはその保護を先に適用し、内容は出力しない。

## 独立した根拠

期限は2026-10-15 00:00 JST以上、2026-10-21 00:00 UTC未満。指示書のSHA256は検証コードに独立して固定し、台帳の自己申告ハッシュを採用しない。正確な登録先、原880と新2637の製品、指示書パス、物理パス、originと新ブランチを呼出しごとに確認する。台帳を変更・再分類せず、空名簿も作らない。

|根拠|SHA256|
|---|---|
|元2636の直接依頼|d9882975ddadfd4126bc429911be92b0f3e4c7d39f208b161319eb7e1789464a|
|新担当2637の有限指示|9fb375dcc3a8cbbf34e679e6d31f661b2bb9271d9a6ca6127d173f0b1a5d3024|
|元880の保持指示|8954d32a53a9f91a7fead3d2dd83c49b17d88666cc80e12477750c819cd80776|

実装はrepair-guard/nextwork_windows.go、通常判定への接続はmain_windows.go。共有スケジューラにも原名簿不在時の新2637/civ6-banseikiだけの入口を接続した。正規guardが拒否すれば起動せず、原名簿が存在する場合は従来の除外を先に適用する。ほかの番号と製品を許可しない。

Go検査とvetは成功。変更された指示・登録・重複番号・期限・origin・物理脱出・作業枝・未知範囲・原依存の保持を検査した。Rust関連検査と配備用ビルドはCargo資源を宣言した独立検査へ渡す。別モデルの確認、必要CI、マージ、正規scrub391/scheduler配備、新2637の実jobとツール/commit/PR/実反映の確認はまだ残る。
3 changes: 3 additions & 0 deletions repair-guard/main_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ func allowed(path string) bool {
if productPathAllowed(absolute) {
return true
}
if nextWorkPathAllowed(absolute) {
return true
}
if boundedSchedulerTree(absolute) {
return true
}
Expand Down
137 changes: 137 additions & 0 deletions repair-guard/nextwork_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package main

// The 2026-10-15 direct request grants one new engineering unit, not recovery
// of the lost registry or a state transition for any of the original 192 rows.
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
)

const nextWorkOwnerSpec = "D:/CC/Tools/todo/specs/2636-959549e314bd449198b5fa7675409c86.md"
const nextWorkOwnerHash = "d9882975ddadfd4126bc429911be92b0f3e4c7d39f208b161319eb7e1789464a"
const nextWorkSpec = "D:/CC/Tools/todo/specs/2637-16aed37d951048e4b1ba002c0158bbfb.md"
const nextWorkHash = "9fb375dcc3a8cbbf34e679e6d31f661b2bb9271d9a6ca6127d173f0b1a5d3024"
const nextWorkOriginalSpec = "D:/CC/Tools/todo/specs/880-77693cd060c34779b732423febb6dfa7.md"
const nextWorkOriginalHash = "8954d32a53a9f91a7fead3d2dd83c49b17d88666cc80e12477750c819cd80776"
const nextWorkRoot = "C:/Users/makhmeto/Desktop/dev/ADJ/civ6-banseiki"
const nextWorkOriginalTree = "C:/Users/makhmeto/Desktop/dev/ADJ/civ6-banseiki-wt/todo-880-pr86"
const nextWorkTree = "C:/Users/makhmeto/Desktop/dev/ADJ/civ6-banseiki-wt/todo-2637-prepare-880-2636"
const nextWorkOrigin = "git@github.com:AutoDevJapan/civ6-banseiki.git"

type nextWorkIO struct {
readObject func(string, int64) (object, error)
read func(string, int64) ([]byte, error)
physical func(string) (string, error)
origin func(string) string
branch func(string) string
protected func(string) bool
digest func([]byte) string
at time.Time
}

func nextWorkPathAllowed(path string) bool {
return nextWorkPathWith(path, nextWorkIO{readObject, readBounded, physical, exactOrigin, exactBranch, legacyMatch, hashBytes, time.Now()})
}

func exactOrigin(root string) string {
return exactGitRead(root, "remote", "get-url", "origin")
}

func exactBranch(root string) string {
return exactGitRead(root, "branch", "--show-current")
}

func exactGitRead(root string, arguments ...string) string {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
command := exec.CommandContext(ctx, "git", append([]string{"-C", root}, arguments...)...)
command.SysProcAttr = &syscall.SysProcAttr{HideWindow: true, CreationFlags: 0x08000000}
command.WaitDelay = time.Second
output, err := command.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(output))
}

func nextWorkPathWith(path string, access nextWorkIO) bool {
// Determine membership BEFORE reading any evidence or invoking Git.
root := ""
for _, candidate := range []string{nextWorkRoot, nextWorkOriginalTree, nextWorkTree} {
if within(candidate, path) {
root = candidate
break
}
}
receipt := same(path, nextWorkOwnerSpec) || same(path, nextWorkSpec) || same(path, nextWorkOriginalSpec)
if root == "" && !receipt {
return false
}
start := time.Date(2026, 10, 14, 15, 0, 0, 0, time.UTC) // October 15 JST
expiry := time.Date(2026, 10, 21, 0, 0, 0, 0, time.UTC)
if access.at.Before(start) || !access.at.Before(expiry) || access.protected(path) {
return false
}
db, err := access.readObject(productStore, 64<<20)
if err != nil {
return false
}
owner, worker, original := itemAt(db, 2636), itemAt(db, 2637), itemAt(db, 880)
if owner == nil || worker == nil || original == nil || owner["project"] != "cc-pc-tools-rs" ||
worker["project"] != "civ6-banseiki" || original["project"] != "civ6-banseiki" ||
!same(str(owner["spec"]), nextWorkOwnerSpec) || !same(str(worker["spec"]), nextWorkSpec) ||
!same(str(original["spec"]), nextWorkOriginalSpec) ||
!same(str(obj(db["projects"])["civ6-banseiki"]), nextWorkRoot) {
return false
}
allocation := obj(worker["scheduler"])
if (str(allocation["worktree"]) != "" && !same(str(allocation["worktree"]), nextWorkTree)) ||
(str(allocation["branch"]) != "" && str(allocation["branch"]) != "scheduler/todo-2637-prepare-880-2636") {
return false
}
// Hashes are independent compiled anchors, never taken from the mutable
// evidence file or the current ledger. Check the exact bytes read here.
for _, anchor := range []struct{ path, hash string }{
{nextWorkOwnerSpec, nextWorkOwnerHash}, {nextWorkSpec, nextWorkHash}, {nextWorkOriginalSpec, nextWorkOriginalHash},
} {
p, e := access.physical(anchor.path)
if e != nil || !same(p, anchor.path) || access.protected(p) {
return false
}
b, e := access.read(anchor.path, 1<<20)
if e != nil || access.digest(b) != anchor.hash {
return false
}
}
resolvedRoot, err := access.physical(nextWorkRoot)
if err != nil || !same(resolvedRoot, nextWorkRoot) || access.protected(resolvedRoot) || access.origin(resolvedRoot) != nextWorkOrigin {
return false
}
if receipt {
return true // exact anchored receipt only
}
resolved, err := access.physical(root)
if err != nil {
// Only this one scheduler allocation may create its as-yet absent tree.
// The physical parent must exist and stay in the specified container.
if !os.IsNotExist(err) || !same(root, nextWorkTree) || !same(path, nextWorkTree) {
return false
}
parent := filepath.Dir(nextWorkTree)
p, e := access.physical(parent)
return e == nil && same(p, parent) && !access.protected(p)
}
if !same(resolved, root) || access.protected(resolved) || access.origin(resolved) != nextWorkOrigin {
return false
}
target, err := access.physical(path)
if same(root, nextWorkTree) && access.branch(resolved) != "scheduler/todo-2637-prepare-880-2636" {
return false
}
return err == nil && within(resolved, target) && !access.protected(target)
}
156 changes: 156 additions & 0 deletions repair-guard/nextwork_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package main

import (
"errors"
"os"
"path/filepath"
"testing"
"time"
)

func nextWorkFixture() (object, nextWorkIO) {
db := object{"projects": object{"civ6-banseiki": nextWorkRoot}, "items": []any{
object{"id": float64(2636), "project": "cc-pc-tools-rs", "spec": nextWorkOwnerSpec},
object{"id": float64(2637), "project": "civ6-banseiki", "spec": nextWorkSpec},
object{"id": float64(880), "project": "civ6-banseiki", "spec": nextWorkOriginalSpec, "status": "doing", "wait_on": []any{float64(2256)}},
}}
anchors := map[string]string{nextWorkOwnerSpec: nextWorkOwnerHash, nextWorkSpec: nextWorkHash, nextWorkOriginalSpec: nextWorkOriginalHash}
access := nextWorkIO{
readObject: func(string, int64) (object, error) { return db, nil },
read: func(path string, _ int64) ([]byte, error) { return []byte(anchors[path]), nil },
physical: func(path string) (string, error) { return path, nil },
origin: func(string) string { return nextWorkOrigin },
branch: func(string) string { return "scheduler/todo-2637-prepare-880-2636" },
protected: func(string) bool { return false },
// Fixture bytes represent independently anchored digests. Production
// always uses SHA256; changed fixture bytes still fail the comparison.
digest: func(b []byte) string { return string(b) },
at: time.Date(2026, 10, 15, 0, 0, 0, 0, time.UTC),
}
return db, access
}

func TestNextWorkRejectsUnknownBeforeAnyEvidenceRead(t *testing.T) {
_, access := nextWorkFixture()
access.readObject = func(string, int64) (object, error) { t.Fatal("unknown scope read ledger"); return nil, nil }
for _, path := range []string{nextWorkRoot + "-other", filepath.Dir(nextWorkRoot), nextWorkTree + "-other", filepath.Join(filepath.Dir(nextWorkTree), "todo-9999-work")} {
if nextWorkPathWith(path, access) {
t.Fatal("unknown resource allowed")
}
}
}

func TestNextWorkIndependentAnchorsExpiryOriginAndRegistry(t *testing.T) {
for _, path := range []string{nextWorkRoot, filepath.Join(nextWorkOriginalTree, "src", "main.rs"), nextWorkTree, nextWorkOwnerSpec, nextWorkSpec, nextWorkOriginalSpec} {
_, access := nextWorkFixture()
if !nextWorkPathWith(path, access) {
t.Fatal("finite scope rejected")
}
}
for _, mutate := range []func(object, *nextWorkIO){
func(_ object, a *nextWorkIO) { a.at = time.Date(2026, 10, 14, 14, 59, 59, 0, time.UTC) },
func(_ object, a *nextWorkIO) { a.at = time.Date(2026, 10, 21, 0, 0, 0, 0, time.UTC) },
func(_ object, a *nextWorkIO) {
a.origin = func(string) string { return "git@github.com:other/civ6-banseiki.git" }
},
func(_ object, a *nextWorkIO) {
a.origin = func(string) string { return "https://github.com/AutoDevJapan/civ6-banseiki.git" }
},
func(_ object, a *nextWorkIO) { a.protected = func(string) bool { return true } },
func(_ object, a *nextWorkIO) {
a.readObject = func(string, int64) (object, error) { return nil, errors.New("unreadable") }
},
func(db object, _ *nextWorkIO) { obj(db["projects"])["civ6-banseiki"] = "C:/other" },
func(db object, _ *nextWorkIO) { itemAt(db, 2637)["spec"] = "different" },
func(db object, _ *nextWorkIO) { itemAt(db, 880)["project"] = "other" },
func(db object, _ *nextWorkIO) { db["items"] = append(db["items"].([]any), itemAt(db, 2637)) },
} {
db, access := nextWorkFixture()
mutate(db, &access)
if nextWorkPathWith(nextWorkRoot, access) {
t.Fatal("changed evidence accepted")
}
}
for _, path := range []string{nextWorkOwnerSpec, nextWorkSpec, nextWorkOriginalSpec} {
_, access := nextWorkFixture()
read := access.read
access.read = func(p string, n int64) ([]byte, error) {
if same(p, path) {
return []byte("changed"), nil
}
return read(p, n)
}
if nextWorkPathWith(nextWorkRoot, access) {
t.Fatal("modified anchored bytes accepted")
}
}
}

func TestNextWorkPhysicalEscapeAndExactAbsentTree(t *testing.T) {
for _, altered := range []string{nextWorkRoot, nextWorkTree, nextWorkSpec, filepath.Join(nextWorkTree, "escape")} {
_, access := nextWorkFixture()
access.physical = func(p string) (string, error) {
if same(p, altered) {
return "C:/outside", nil
}
return p, nil
}
if nextWorkPathWith(filepath.Join(nextWorkTree, "escape"), access) {
t.Fatal("physical escape accepted")
}
}
_, access := nextWorkFixture()
access.physical = func(p string) (string, error) {
if same(p, nextWorkTree) {
return "", os.ErrNotExist
}
return p, nil
}
if !nextWorkPathWith(nextWorkTree, access) {
t.Fatal("exact planned allocation rejected")
}
if nextWorkPathWith(filepath.Join(nextWorkTree, "child"), access) {
t.Fatal("missing child allowed")
}
access.physical = func(p string) (string, error) {
if same(p, nextWorkTree) {
return "", os.ErrNotExist
}
if same(p, filepath.Dir(nextWorkTree)) {
return "C:/outside", nil
}
return p, nil
}
if nextWorkPathWith(nextWorkTree, access) {
t.Fatal("escaped allocation parent accepted")
}
}

func TestNextWorkLeavesOriginalStateUntouched(t *testing.T) {
db, access := nextWorkFixture()
original := itemAt(db, 880)
if !nextWorkPathWith(nextWorkRoot, access) {
t.Fatal("scope rejected")
}
if original["status"] != "doing" || original["wait_on"].([]any)[0] != float64(2256) {
t.Fatal("original history or dependency changed")
}
}

func TestNextWorkRejectsDifferentAllocationAndBranch(t *testing.T) {
for _, mutate := range []func(object, *nextWorkIO){
func(db object, _ *nextWorkIO) {
itemAt(db, 2637)["scheduler"] = object{"worktree": nextWorkTree + "-other"}
},
func(db object, _ *nextWorkIO) {
itemAt(db, 2637)["scheduler"] = object{"branch": "scheduler/todo-9999-work"}
},
func(_ object, a *nextWorkIO) { a.branch = func(string) string { return "main" } },
} {
db, a := nextWorkFixture()
mutate(db, &a)
if nextWorkPathWith(nextWorkTree, a) {
t.Fatal("different allocation or branch allowed")
}
}
}
Loading