-
Notifications
You must be signed in to change notification settings - Fork 0
Feature: Project initialization and target architecture #21
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
base: main
Are you sure you want to change the base?
Changes from all commits
e04660f
2fb8293
886ff6f
ec83a96
266f811
716258e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Project: Chronos for Azure | ||
|
|
||
| Chronos for Azure is a lightweight Azure Functions engine that starts and stops Azure Virtual Machines based on CRON schedules supplied via Azure Resource Tags. Users tag VMs with start and/or stop expressions; Chronos discovers those tags, schedules the corresponding lifecycle events, and executes them — no per-VM configuration or code changes required. The goal is zero-friction, tag-driven cost optimization. | ||
|
|
||
| The repository was originally written on .NET 6 (C# in-process Durable Functions, Bicep) and is undergoing a full rewrite. Assume nothing from the legacy code — patterns, tag names, and architecture are being redesigned. | ||
|
|
||
| ## Target stack (authoritative) | ||
|
|
||
| - **Runtime**: .NET 10, Azure Functions isolated worker | ||
| - **Hosting**: Flex Consumption plan (Linux) — required for .NET 10 + Durable on Linux, scales to zero | ||
| - **Orchestration**: Azure Durable Functions — **Durable Entity per VM + eternal per-VM orchestrator + activities** (decided; see `README.md` → Architecture) | ||
| - **Durable backend**: Azure Storage (default provider) for v1; Durable Task Scheduler is a future migration if scale demands it | ||
| - **Discovery**: Azure Resource Graph (single KQL query, multi-subscription-ready) | ||
| - **IaC**: Terraform (AzureRM provider), under `infra/` | ||
| - **Deployment**: Azure Developer CLI (`azd`) headless, orchestrating the Terraform deployment. An `azure.yaml` will be added in a later feature | ||
| - **Source layout**: `src/` for Functions code, `infra/` for Terraform, `deploy/` currently held empty via `.gitkeep` pending the infra decision | ||
|
|
||
| None of the above code exists yet. Project initialization establishes conventions and architecture; implementation lands in subsequent features. | ||
|
|
||
| ## Architecture (decided) | ||
|
|
||
| Four components — full description, diagram, and rationale live in [`README.md`](../README.md) (`## Architecture`). Summary: | ||
|
|
||
| - **`DiscoveryTimer`** (5-min Timer trigger) — ARG query for tagged VMs → signals each `VmScheduleEntity` with the materialized schedule, or `Clear()` for vanished tags. Spawns the orchestrator for actionable entities that don't yet have one. | ||
| - **`VmScheduleEntity`** — Durable Entity keyed by ARM resource ID. Holds start CRON, stop CRON, timezone, exclusion flag, last action. Source of truth for per-VM intent. | ||
| - **`VmScheduler`** — eternal Durable Orchestrator, one per VM. Reads entity, arms a durable timer to the next CRON occurrence, races it against the `ScheduleChanged` external event. On timer: calls start/stop activity. On event: recomputes. Always `ContinueAsNew`. | ||
| - **Start / Stop activities** — idempotent wrappers over `Azure.ResourceManager.Compute` via user-assigned managed identity. | ||
|
|
||
| **Tag mutation mechanism (every case): discovery signals entity → entity raises `ScheduleChanged` → orchestrator reacts.** Edits update the schedule and re-arm the timer; full removal makes the orchestrator exit cleanly. No special cases. | ||
|
|
||
| The standalone Durable Task SDK was evaluated and rejected — see the README section. Do not propose it again without a concrete reason. | ||
|
|
||
| ## Scheduling scenarios the engine must support | ||
|
|
||
| The exact tag names are **not yet defined**. Refer to these scenarios by behavior: | ||
|
|
||
| 1. **Stop-only**: VM has a stop/deallocate schedule but no start schedule → Chronos stops it on schedule and never starts it. | ||
| 2. **Start-only**: VM has a start schedule but no stop schedule → Chronos starts it on schedule. | ||
| 3. **Start + stop**: VM has both schedules → Chronos runs both. | ||
| 4. **Exclusion**: VM has an exclusion tag → Chronos ignores the VM entirely, even if start/stop tags are present. Users must not be forced to remove schedule tags to opt out. | ||
| 5. **Timezone override**: VM has a timezone tag → Chronos parses it and interprets the CRON expressions in that timezone. Invalid/unparseable timezones must be handled gracefully. | ||
|
|
||
| Do **not** invent concrete tag names. When implementing a feature that needs them, ask the user first. | ||
|
|
||
| ## Workflow & conventions | ||
|
|
||
| ### Branching | ||
| - `main` is protected — **never** commit or push to it directly. | ||
| - Every change (code, infra, docs) happens on a feature branch and lands via pull request. | ||
|
|
||
| ### Commit message prefixes | ||
| - `feat:` — new feature | ||
| - `bug:` — hotfix | ||
| - `docs:` — documentation | ||
| - `refactor:` — refactoring of existing functionality | ||
|
|
||
| ### Pull request title prefixes | ||
| - `Feature: …` | ||
| - `Documentation: …` | ||
| - `Refactoring: …` | ||
|
|
||
| (No dedicated PR prefix for hotfixes — use `Feature:` or ask the user.) | ||
|
|
||
| ### Commit approval | ||
|
|
||
| **Always ask before creating a commit.** Before running `git commit`, present the proposed commit message to the user and wait for explicit confirmation. Never commit unilaterally, even when changes are obviously ready. This applies to every commit — the user's approval of a prior commit does not roll forward to the next. | ||
|
|
||
| Do **not** add Claude attribution, co-author trailers, or "Generated with Claude Code" footers to commit messages or PR bodies. The harness is configured (`.claude/settings.json` → `attribution`) to suppress these; don't reintroduce them manually. | ||
|
|
||
| ## Working-style notes for Claude | ||
|
|
||
| - **Use current Microsoft documentation.** Patterns for Azure Functions have shifted substantially since .NET 6 (isolated worker model, new Durable Functions APIs, DI conventions). Do not replicate legacy in-process patterns. When in doubt, fetch current Microsoft Learn docs for Azure Functions, Durable Functions, .NET 10, and the Terraform AzureRM provider. | ||
| - **Architecture is fixed; do not relitigate.** The decisions in "Target stack" and "Architecture (decided)" — Durable Entities + eternal per-VM orchestrator on Flex Consumption with Azure Storage backend — were made deliberately and are documented in `README.md`. Do not propose alternatives (pure Timer trigger, ad-hoc orchestrations per event, standalone Durable Task SDK, external state store) without a concrete new reason. | ||
| - **Open questions remain — ask before deciding these.** Concrete Chronos tag *names*, the manual-override surface (HTTP API shape), and Event Grid integration timing are all undecided. When implementing a feature that touches these, ask first. | ||
| - **No speculative scaffolding.** Don't generate full Function apps or Terraform modules without an explicit go-ahead for that feature. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | ||
| "attribution": { | ||
| "commit": "", | ||
| "pr": "" | ||
| }, | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(git status)", | ||
| "Bash(git status *)", | ||
| "Bash(git log)", | ||
| "Bash(git log *)", | ||
| "Bash(git diff)", | ||
| "Bash(git diff *)", | ||
| "Bash(git show)", | ||
| "Bash(git show *)", | ||
| "Bash(git branch)", | ||
| "Bash(git branch *)", | ||
| "Bash(git remote -v)", | ||
| "Bash(git remote show *)", | ||
| "Bash(git blame *)", | ||
| "Bash(git reflog)", | ||
| "Bash(git reflog *)", | ||
| "Bash(git rev-parse *)", | ||
| "Bash(git describe *)", | ||
|
|
||
| "Bash(git add *)", | ||
| "Bash(git commit *)", | ||
| "Bash(git push)", | ||
| "Bash(git push *)", | ||
| "Bash(git checkout *)", | ||
| "Bash(git switch *)", | ||
| "Bash(git restore *)", | ||
| "Bash(git stash)", | ||
| "Bash(git stash *)", | ||
| "Bash(git pull)", | ||
| "Bash(git pull *)", | ||
| "Bash(git fetch)", | ||
| "Bash(git fetch *)", | ||
| "Bash(git merge *)", | ||
| "Bash(git rebase *)", | ||
|
|
||
| "Bash(gh issue list*)", | ||
| "Bash(gh issue view*)", | ||
| "Bash(gh issue status*)", | ||
| "Bash(gh pr list*)", | ||
| "Bash(gh pr view*)", | ||
| "Bash(gh pr status*)", | ||
| "Bash(gh pr diff*)", | ||
| "Bash(gh pr checks*)", | ||
| "Bash(gh repo view*)" | ||
| ] | ||
| } | ||
| } |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,9 +1,9 @@ | ||||||
| { | ||||||
| "azureFunctions.deploySubpath": "src/AzureChronos.Functions/bin/Release/net6.0/publish", | ||||||
| "azureFunctions.deploySubpath": "src/bin/Release/net10.0/publish", | ||||||
| "azureFunctions.projectLanguage": "C#", | ||||||
| "azureFunctions.projectRuntime": "~4", | ||||||
| "debug.internalConsoleOptions": "neverOpen", | ||||||
| "azureFunctions.projectSubpath": "src/AzureChronos.Functions", | ||||||
| "azureFunctions.preDeployTask": "publish (functions)", | ||||||
| "dotnet.defaultSolution": "src/AzureChronos.sln" | ||||||
|
||||||
| "dotnet.defaultSolution": "src/AzureChronos.sln" | |
| "dotnet.defaultSolution": "src/AzureChronos.slnx" |
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.
azureFunctions.deploySubpathpoints tosrc/bin/Release/net10.0/publish, but the Functions project publishes undersrc/AzureChronos.Functions/bin/Release/net10.0/publish. As written, VS Code deployments will likely pick up a non-existent folder.