mirror of
https://github.com/block/buzz.git
synced 2026-11-02 23:41:08 +00:00
## Summary - inject bounded project-home identity and repository context into managed agent sessions - add project-aware CLI flows for creating projects, repositories, issues, and related channels - match project homes through the existing relay query surface, then filter channel metadata client-side This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations. ## Testing - `cargo fmt --all -- --check` - `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings` - `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test ignored - full pre-push gate passed ## Post-Deploy Monitoring & Validation - validate project-home agent context and project-aware CLI commands against a staging relay - healthy signals: project context matches the active channel, explicit repo coordinates remain stable, and normal channels receive no project block - failure signals: cross-channel project context, duplicate project creation, or commands targeting an unrelated repository; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import type { RelayEvent } from "@/shared/api/types";
|
|
import { KIND_PROJECT_ANNOUNCEMENT } from "@/shared/constants/kinds";
|
|
import {
|
|
isValidProjectChannelId,
|
|
MAX_PROJECT_RELATED_CHANNELS,
|
|
PROJECT_RELATED_CHANNEL_TAG,
|
|
validateProjectEventEnvelope,
|
|
} from "@/features/projects/projectModels";
|
|
import type { ProjectEventTemplate } from "./projectCreation";
|
|
|
|
/**
|
|
* Appends a `buzz-related-channel` tag to a live project head. Every other
|
|
* tag is preserved so adding a stream cannot erase unknown metadata.
|
|
*/
|
|
export function buildProjectRelatedChannelPatchTemplate({
|
|
channelId,
|
|
liveHead,
|
|
ownerPubkey,
|
|
}: {
|
|
channelId: string;
|
|
liveHead: RelayEvent;
|
|
ownerPubkey: string;
|
|
}): { alreadyBound: boolean; project: ProjectEventTemplate } {
|
|
const normalizedOwner = ownerPubkey.trim().toLowerCase();
|
|
if (normalizedOwner !== liveHead.pubkey.toLowerCase()) {
|
|
throw new Error("Only the project owner can add channels.");
|
|
}
|
|
const normalizedChannelId = channelId.trim();
|
|
if (!isValidProjectChannelId(normalizedChannelId)) {
|
|
throw new Error("Project channel is invalid.");
|
|
}
|
|
const homeChannelId = liveHead.tags.find(
|
|
(tag) => tag[0] === "buzz-channel",
|
|
)?.[1];
|
|
if (homeChannelId === normalizedChannelId) {
|
|
throw new Error("That channel is already this project's home.");
|
|
}
|
|
const existingRelated = liveHead.tags
|
|
.filter((tag) => tag[0] === PROJECT_RELATED_CHANNEL_TAG)
|
|
.map((tag) => tag[1])
|
|
.filter((value): value is string => Boolean(value));
|
|
if (existingRelated.includes(normalizedChannelId)) {
|
|
validateProjectEventEnvelope(liveHead.tags, liveHead.content);
|
|
return {
|
|
alreadyBound: true,
|
|
project: {
|
|
kind: KIND_PROJECT_ANNOUNCEMENT,
|
|
content: liveHead.content,
|
|
tags: liveHead.tags.map((tag) => [...tag]),
|
|
},
|
|
};
|
|
}
|
|
if (existingRelated.length >= MAX_PROJECT_RELATED_CHANNELS) {
|
|
throw new Error(
|
|
`A project cannot contain more than ${MAX_PROJECT_RELATED_CHANNELS} extra channels.`,
|
|
);
|
|
}
|
|
const tags = [
|
|
...liveHead.tags.map((tag) => [...tag]),
|
|
[PROJECT_RELATED_CHANNEL_TAG, normalizedChannelId],
|
|
];
|
|
validateProjectEventEnvelope(tags, liveHead.content);
|
|
return {
|
|
alreadyBound: false,
|
|
project: {
|
|
kind: KIND_PROJECT_ANNOUNCEMENT,
|
|
content: liveHead.content,
|
|
tags,
|
|
},
|
|
};
|
|
}
|