Skip to content

Repository files navigation

Azure Project Log

A full-stack TypeScript workspace for tracing developer contributions across an Azure DevOps Services organization. It connects each contribution to its pull request, source and target branches, merge destination, builds, and deployment status. The backend is an Express modular monolith and the frontend is a React single-page application built with Vite.

Product scope

A contribution is the primary delivery record in Azure Project Log. Azure Boards tasks and other work-item types provide the planning, ownership, iteration, priority, and effort context around that delivery story. The dashboard is designed to answer:

  • Who contributed the change, through which commits and pull request?
  • What was the source branch, target branch, and final merge status?
  • Which build or pipeline included the contribution?
  • Which environments received it, and is it live, pending, failed, or rolled back?
  • Which specifications, decisions, or notes from tools such as Craft explain the change?
  • Which work is open, overdue, completed, or unassigned, and who currently owns it?
  • Which tasks were completed after a date, asked in plain language and verified against live data?

Prerequisites

  • Node.js 22.12 or newer
  • npm 10 or newer
  • An Azure DevOps Services organization

Personal access token setup

A PAT is the quickest local-development connection and does not require an Entra app registration:

  1. In Azure DevOps, open User settings -> Personal access tokens.
  2. Create an organization-scoped token with Project and Team: Read, Work Items: Read, Code: Read, and Build: Read permissions. Reading YAML environment deployment history may additionally require Environment: Read & manage.
  3. Open the Azure Project Log project panel, enter the organization name if it is not configured by the backend, paste the PAT, and choose Connect with PAT.

The browser sends the PAT once to Express. It is never returned to the browser, written to browser storage, or logged. The temporary backend session expires after eight hours. Local Node development keeps it in memory; the Cloudflare runtime keeps only this expiring server-side session in a Durable Object so Worker restarts do not randomly sign users out. Treat a PAT like a password, grant the smallest scope possible, and revoke it when it is no longer needed. See Microsoft's PAT guidance.

Azure Project Log accounts and saved Azure connections

Users can create an Azure Project Log account with a name, email, password, Azure DevOps organization, and PAT. Registration validates the PAT against Azure DevOps before writing the account. The password is hashed with bcrypt (12 rounds by default); the PAT is separately encrypted with authenticated AES-256-GCM because Azure Project Log must recover it to call Azure DevOps. Neither value is returned to React or written to logs.

Generate the server-only PAT encryption key once:

npm run account:keygen --workspace @n3/backend

The key is written to the gitignored backend .env. Back it up as an application secret: losing it makes saved PATs unrecoverable. Local Express stores accounts in apps/backend/data/n3-project-log.db. Cloudflare stores them in the n3-project-log-db D1 binding, and the deployment command applies the versioned SQL migrations. Passport Local verifies email/password without creating a Passport session; Azure Project Log continues to use its opaque, expiring HttpOnly session cookie.

Cloudflare's free Worker plan has a 10 ms CPU limit per request, which is not suitable for reliable bcrypt work at a secure cost. Use Workers Paid for account registration/login, or keep the Node backend on a conventional host. One-time PAT and Microsoft connection flows remain available when account persistence is not configured.

Before exposing self-service accounts publicly, add edge rate limits for registration, login, PAT connection, and chat routes. Also add email verification and account-recovery flows if email ownership is meant to carry security authority. Those controls are deployment/product policy and are intentionally not approximated with per-process memory, which would be inconsistent across Worker isolates.

Optional Microsoft Entra setup

Azure Project Log uses the OAuth 2.0 authorization-code flow through Microsoft Entra. The signed-in user's delegated token is kept on the Express backend and is never returned to React.

  1. In Microsoft Entra admin center, create an app registration for Azure Project Log.
  2. Under Authentication, add the Web platform and this local redirect URI: http://localhost:3000/api/v1/auth/microsoft/callback.
  3. Under API permissions, add the delegated Azure DevOps permission required to act as the signed-in user. Grant consent if your tenant requires administrator consent.
  4. Create a client secret and copy its value immediately.
  5. Copy apps/backend/.env.example to apps/backend/.env and fill in the organization, tenant, client, and secret values.

Microsoft's current setup reference is Use Microsoft Entra OAuth to authenticate to Azure DevOps. Microsoft recommends Entra user delegation for interactive web applications and PATs only as a higher-risk fallback; see its Azure DevOps Entra authentication overview.

Cloud project assistant

The compact floating Ask Taskie chat uses DeepSeek V4 Flash through the OpenAI-compatible OpenCode Go API. Add the OpenCode Go API key only to the gitignored apps/backend/.env:

CLOUD_LLM_BASE_URL=https://opencode.ai/zen/go/v1
CLOUD_LLM_MODEL=deepseek-v4-flash
CLOUD_LLM_FALLBACK_MODELS=deepseek-v4-pro
CLOUD_LLM_API_KEY=your_opencode_go_key

Use deepseek-v4-pro instead when Taskie needs the higher-quality DeepSeek tier. Then restart Express.

The browser never receives the key or calls the model endpoint directly. Express runs a LangGraph tool loop through LangChain. Taskie can choose among typed, read-only tools for exact work items, work-item search, completion dates, recent contributions, and delivery evidence. Each tool is closed over the authenticated Azure connection and selected project, so the model cannot choose a different organization, project, or credential. Azure PATs and Entra access tokens are never sent to the cloud model. Verified Azure URLs are returned separately as clickable chat evidence.

Work-item filters run across the complete WIQL match set. The backend hydrates Azure work items in API-safe batches, reports the complete match count, and sends only a bounded evidence page to the model. Contribution and deployment questions remain explicitly scoped to recent history because their status is derived from multiple PR, build, pipeline, and deployment APIs; Taskie reports that scope instead of presenting a recent window as a complete-project count.

There is no hard-coded intent whitelist: Taskie can handle conversational follow-ups and decide which tool or tools are needed. Project claims must still come from current tool evidence, and graph/model/ tool-call limits prevent runaway requests. Examples include “What tasks were completed after 26 June 2026?”, “Is work item #31412 resolved?”, and “Was that PR deployed?”.

Optional Craft connection

Craft is an optional integration and does not require an OAuth app registration:

  1. In the Craft space that should receive the tasks, open Imagine -> API connections.
  2. Create a Full Space connection that can list documents, create documents, and add blocks.
  3. Copy the generated https://connect.craft.do/... API connection URL.
  4. Connect Azure DevOps in Azure Project Log, choose Connect Craft on the app shelf, and paste that URL.

Once connected, every work-item card gets a Send to Craft action. The user can create a new document or select an existing document. Existing-document mode appends a separated task section at the end; it never replaces current Craft content. Azure Project Log reloads the work item from Azure DevOps at send time so Craft receives the latest state, owner, paths, estimates, tags, dates, and source link.

The generated Craft URL is a credential. React submits it once, clears the field after success, does not store it in browser storage, and never receives it back. Express accepts only HTTPS connect.craft.do URLs. Account connections are encrypted at rest and restored after the user signs in again; PAT-only and Microsoft-only connections remain scoped to their expiring backend session. A disconnect removes the applicable saved or temporary connection. See Craft's official API setup guide and Full Space API reference.

QNE App Scheduler contract

The Express backend exposes exactly two server-to-server scheduler routes outside the versioned API prefix:

  • GET /scheduler/fields returns the n3-daily-cfo app contract, the dailyOverview datasource, its dynamic fields, and its default Telegram HTML template.
  • GET /scheduler/data?datasourceKey=dailyOverview&filters={urlEncodedJson} returns current Azure DevOps delivery values. filters is optional and supports projectId, taskLimit, and contributionLimit. Work-item summaries use the latest 10 items by default; taskLimit can explicitly widen that window.

Every request must carry the five X-QNE-Scheduler-* headers. Express verifies the configured key ID, signature version v1, a Unix timestamp within 300 seconds, and the lowercase SHA-256 of the empty GET body. It signs this exact canonical value with HMAC-SHA256 and compares the base64url signature in constant time:

METHOD + "\n" + PATH_AND_QUERY + "\n" + TIMESTAMP + "\n" + CONTENT_SHA256

The raw encoded path and query are signed, including query order and URL encoding. QNE credentials must only be added to the gitignored apps/backend/.env; the example file deliberately contains no credential values. They are never part of React, a VITE_ variable, a response, or application log context.

Scheduled calls do not carry a user's browser session. To return tenant-authorized live data, configure a dedicated least-privilege QNE_SCHEDULER_AZURE_DEVOPS_PAT and AZURE_DEVOPS_ORGANIZATION. Set QNE_SCHEDULER_DEFAULT_PROJECT_ID for calls without filters, or provide an accessible projectId in the encoded filters. The backend verifies that the project is visible to that credential, then queries current work items, contributions, builds, and deployment evidence. Missing authorization or upstream failures return non-2xx errors; no sample data is used.

Cloudflare deployment

Azure Project Log uses a Cloudflare Pages front door plus an internal Worker backend. Pages serves the cached React SPA, while Pages Functions forward only /api/* and /scheduler/* to the Express Worker through a same-account Service Binding. Azure DevOps, cloud-model, Craft, and QNE credentials remain server-side. An internal SQLite-backed Durable Object stores only expiring authentication and Craft session records; it is not exposed as a public route. The Worker still serves the complete app on workers.dev as a fallback.

  1. Authenticate Wrangler once from the repository root:

    npx wrangler login
  2. Keep production secrets in the gitignored apps/backend/.env. CLOUD_LLM_BASE_URL, CLOUD_LLM_MODEL, CLOUD_LLM_API_KEY, and ACCOUNT_PAT_ENCRYPTION_KEY are required. Add the Entra and QNE variables from apps/backend/.env.example only when those integrations are enabled. PAT users can continue entering their organization and PAT in the browser.

  3. Validate the complete production bundle without uploading it:

    npm run build:cloudflare
  4. Deploy the Express Worker, migrations, secrets, and Pages front door together:

    npm run deploy:cloudflare

The deploy command allowlists server-only values from apps/backend/.env, applies pending additive D1 migrations before publishing code that depends on them, passes the values to Wrangler as encrypted Worker secrets, and deletes its temporary gitignored secrets file even if deployment fails. Pages owns no application secrets; its N3_API Service Binding invokes the Worker without a public network hop.

Continuous deployment

Pushing a verified commit to main starts .github/workflows/deploy.yml. After one shared release check, GitHub Actions deploys Pages and the Worker in separate jobs. A backend deployment failure therefore cannot prevent the verified frontend build from reaching Pages, and a Pages failure does not block the Worker deployment.

Configure only CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN as GitHub Actions repository secrets. Worker application secrets and server-only dashboard variables remain in Cloudflare and are preserved across code deployments; they are not duplicated in GitHub. Bootstrap or rotate Worker configuration intentionally with the local npm run deploy:cloudflare command and a complete gitignored apps/backend/.env file. Wrangler validates the required remote secret bindings before a CD deployment succeeds, while the CD command preserves remote dashboard variables with --keep-vars.

Both deployment jobs receive the Cloudflare deployment credentials. Neither job receives Azure DevOps, account encryption, cloud-model, Microsoft Entra, Craft, or QNE scheduler secrets. The frontend publishes the compiled SPA without browser-accessible application secrets. The workflow can also be started manually from GitHub Actions.

The production Pages project is n3-project-log-web.pages.dev. To keep Namecheap authoritative for mdfahimhossen.space, associate azure.mdfahimhossen.space with the Pages project in Cloudflare and then add this record in Namecheap: CNAME azure -> n3-project-log-web.pages.dev. Do not add the CNAME before Pages accepts the custom hostname. Cloudflare Pages provisions the hostname certificate while Namecheap continues to manage the domain's DNS records.

Microsoft sign-in needs one post-deployment configuration step: add https://<worker-host>/api/v1/auth/microsoft/callback as a Web redirect URI in Entra, set the same value as MICROSOFT_ENTRA_REDIRECT_URI in the backend .env, and redeploy. PAT, Craft, Ask Taskie, and the scheduler do not require a browser-facing secret or a separate frontend deployment.

Run locally

Install dependencies and prepare the backend environment from the repository root:

npm install
Copy-Item apps/backend/.env.example apps/backend/.env
npm run account:keygen --workspace @n3/backend
npm run dev

The development command starts both applications:

  • React: http://localhost:5173
  • Express API: http://localhost:3000/api/v1
  • Health check: http://localhost:3000/api/v1/health
  • Scheduler contract: http://localhost:3000/scheduler/fields
  • Cloud model API: the server-only CLOUD_LLM_BASE_URL value

Open React and connect with either a PAT or Microsoft. The project selector validates the credential against Azure DevOps before creating the backend session, then loads the projects that credential can access. Selecting a project loads a searchable Azure Boards task overview and a separate contribution trail, plus a compact floating Ask Taskie chat for live work-item questions by date, ID, or title. If Craft is connected, task cards can also create or append Craft documents. The trail matches recent pull-request merge/source commits to build source versions and build-change history, then attaches YAML stage and environment deployment evidence. Deployment is not required for any local connection flow; the Microsoft callback URI only needs to match the Entra app registration exactly when that option is enabled.

Vite proxies browser requests beginning with /api to Express. After Microsoft calls the backend redirect URI, Express creates an opaque HttpOnly, SameSite=Lax session cookie and redirects the browser back to React.

Commands

Command Purpose
npm run dev Start the backend and frontend in watch mode
npm run dev:backend Start only the Express API
npm run dev:frontend Start only the React application
npm run dev:cloudflare Run the built SPA and API in the Worker runtime
npm run account:keygen --workspace @n3/backend Generate account PAT encryption key
npm run build Build all workspaces
npm run build:cloudflare Validate the Cloudflare production bundle
npm run deploy:cloudflare Deploy the full stack and server secrets
npm start Run the compiled backend
npm test Run workspace tests
npm run lint Lint all workspaces
npm run typecheck Type-check all workspaces
npm run format Format the repository
npm run check Run formatting, types, lint, tests, and builds

Workspace architecture

apps/
+-- backend/                  # Express + TypeScript modular monolith
|   +-- src/
|   +-- migrations/            # D1 account schema migrations
|   +-- tests/
|   +-- .env.example
|   +-- wrangler.jsonc         # Worker, assets, and Durable Object bindings
|   `-- package.json
`-- frontend/                 # React + TypeScript single-page application
    +-- src/
    |   +-- app/              # Application shell and composition
    |   +-- features/         # Authentication and project features
    |   +-- index.css         # Global design tokens and base styles
    |   `-- main.tsx          # Browser entry point
    +-- .env.example
    +-- vite.config.ts
    `-- package.json

The root package.json owns npm workspace orchestration and repository-wide formatting. Each application owns its runtime dependencies and tooling. A shared package will only be introduced when the frontend and backend have stable contracts worth sharing.

Backend

apps/backend/src/
+-- core/                     # Framework-independent shared capabilities
|   +-- config/               # Validated environment configuration
|   +-- errors/               # Application error types
|   `-- logger/               # Central structured logging boundary
+-- infrastructure/
|   +-- cloudflare/           # Durable session runtime and internal client
|   `-- http/                 # Service-wide Express infrastructure
|       +-- middleware/
|       +-- routes/
|       `-- types/
+-- modules/                  # Business capabilities
|   +-- account/              # Passport, bcrypt, PAT encryption, SQLite/D1
|   +-- auth/
|   |   +-- domain/           # Session store and Microsoft client ports
|   |   +-- application/      # Microsoft/PAT authorization orchestration
|   |   +-- infrastructure/   # MSAL client and temporary in-memory store
|   |   `-- public/           # Auth routes, controller, cookie, middleware
|   +-- chat/                 # LangGraph agent, typed tools, and evidence response contract
|   +-- contribution/        # PR, build, YAML stage, and deployment correlation
|   +-- craft/               # Session connection and task-to-document handoff
|   +-- health/
|   |   `-- public/
|   +-- project/
|       +-- domain/           # Project model and repository contract
|       +-- application/      # Project-list use case
|       +-- infrastructure/   # Azure DevOps Services REST implementation
|       `-- public/           # Project controller and protected route
|   +-- scheduler/            # HMAC contract and live server-to-server datasource
|   `-- task/                 # Azure Boards WIQL and work-item overview
+-- app.ts                    # Express application assembly
|-- worker.ts                 # Cloudflare Express/asset runtime entry
`-- server.ts                 # Process startup and graceful shutdown

Business capabilities follow public -> application -> domain. Microsoft and Azure DevOps details stay in module infrastructure so they do not leak into use cases or response contracts.

Frontend

The frontend contains the application shell and separate features/auth, features/projects, features/tasks, features/contributions, features/chat, and features/craft boundaries. The selected-project workspace provides tabbed task and contribution views with loading, empty, error, retry, search, comprehensive filter states, and 10-item result pages. Ask Taskie is a compact floating chat that remains available across both project views. Craft owns its connection card and task export dialog.

Connection flow

Microsoft: React -> Microsoft Entra -> callback -> Express session
PAT:       React -> POST /auth/pat -> credential validation -> Express session
Projects:  React -> GET /projects + HttpOnly session cookie -> Azure DevOps
Tasks:     React -> GET /projects/:id/tasks -> WIQL IDs -> work-items batch
Ask Taskie: React -> Express -> LangGraph tool loop -> Azure facts -> grounded answer + Azure URLs
Trail:     React -> GET /projects/:id/contributions -> correlate PR/build/deployment evidence
Craft:     React -> Express session -> Craft documents/blocks API
Scheduler: QNE -> signed top-level Express route -> tenant-authorized Azure DevOps data

Project pages are retrieved until exhausted, de-duplicated by project ID, sorted by name, and returned through GET /api/v1/projects. Contribution results are calculated live over a bounded window of recent pull requests and builds. No database or application data cache is used yet.

AccountingOnline uses YAML multi-stage pipelines, so Azure Pipelines environments and their deployment records are authoritative evidence when available. QuinnyAI is deployed by an external script; Azure Project Log deliberately reports those deployments as unverified until that script sends a future deployment receipt containing the commit, environment, result, and timestamp.

For later hotfix builds, direct SHA equality is insufficient. Azure Project Log downloads the parent links for the deployed build commit with Azure DevOps commitsbatch, builds a bounded commit graph, and runs an iterative depth-first search from the deployed SHA. A contribution is included when its completed PR merge commit (or the source commit of an active PR) is reachable as an ancestor. Missing or truncated DFS history falls back to Azure DevOps' merge-base comparison endpoint; only when both sources are unavailable is the result reported as unavailable evidence.

The Node development runtime intentionally keeps sessions, PAT credentials, and the MSAL token cache in memory, so a local backend restart signs users out. Cloudflare swaps those session-store adapters for a Durable Object so multiple Worker isolates share the same expiring server-side records. The account database stores identity records and encrypted Azure connection credentials only. The later data phase still owns synchronized project history, caching, and authorization policies beyond basic account login; current connections authorize Azure DevOps access only.

API conventions

  • GET /api/v1/auth/microsoft/login starts Microsoft sign-in.
  • GET /api/v1/auth/microsoft/callback completes sign-in and sets the backend session cookie.
  • POST /api/v1/auth/register validates Azure access, creates an Azure Project Log account, and signs in.
  • POST /api/v1/auth/login authenticates an Azure Project Log account through Passport Local.
  • POST /api/v1/auth/pat validates a PAT and creates a temporary backend session.
  • GET /api/v1/auth/session returns safe connection state; it never returns a token.
  • POST /api/v1/auth/logout invalidates the backend session.
  • GET /api/v1/craft/session returns safe Craft connection state without its API URL.
  • POST /api/v1/craft/connect validates and retains a Craft API connection URL in the backend.
  • POST /api/v1/craft/disconnect removes the Craft connection from the current session.
  • GET /api/v1/craft/documents lists documents available to the Craft connection.
  • POST /api/v1/craft/task-exports creates a document or appends a task to an existing document.
  • GET /api/v1/projects requires a valid backend session.
  • GET /api/v1/projects/:projectId/tasks?limit=10 returns recent Azure Boards work items. The default is 10; the UI widens the bounded window only when the user chooses Load 10 more.
  • POST /api/v1/projects/:projectId/chat answers date, ID, title, and status questions from live Azure data.
  • GET /api/v1/projects/:projectId/contributions?limit=20 returns the recent delivery graph.
  • GET /scheduler/fields returns the signed QNE datasource and field contract.
  • GET /scheduler/data?datasourceKey=...&filters=... returns signed, live scheduler values.
  • Successful responses use { "success": true, "data": ... }.
  • Scheduler success responses use the exact QNE top-level contract shapes instead of the API wrapper.
  • Errors use { "success": false, "error": { "code", "message", "requestId" } }.
  • Every response includes an x-request-id header.
  • JSON and URL-encoded bodies are limited to 1 MB.
  • API responses are non-cacheable and include defensive browser headers. Credentialed browser writes with an explicitly foreign Origin or Sec-Fetch-Site: cross-site are rejected.

Environment variables

Backend variables belong in apps/backend/.env:

Name Default Description
NODE_ENV development Runtime environment
PORT 3000 Express HTTP port
API_PREFIX /api/v1 Versioned API base path
FRONTEND_URL http://localhost:5173 Redirect target after Microsoft sign-in
ACCOUNT_DATABASE_PATH ./data/n3-project-log.db Local SQLite account database
ACCOUNT_BCRYPT_ROUNDS 12 Password hashing work factor (10-14)
ACCOUNT_PAT_ENCRYPTION_KEY none Server-only 32-byte base64url AES key
AZURE_DEVOPS_ORGANIZATION none Default organization name
AZURE_DEVOPS_API_VERSION 7.1 Azure DevOps REST API version
AZURE_DEVOPS_REQUEST_TIMEOUT_MS 10000 Upstream timeout in milliseconds
CLOUD_LLM_BASE_URL https://opencode.ai/zen/go/v1 Server-only compatible API base URL
CLOUD_LLM_MODEL deepseek-v4-flash Tool-capable model used by Ask Taskie
CLOUD_LLM_FALLBACK_MODELS deepseek-v4-pro Comma-separated automatic fallback models
CLOUD_LLM_API_KEY none Server-only cloud-model API key
CLOUD_LLM_REQUEST_TIMEOUT_MS 60000 Cloud model timeout in milliseconds
MICROSOFT_ENTRA_TENANT_ID none Entra directory/tenant ID
MICROSOFT_ENTRA_CLIENT_ID none Entra application/client ID
MICROSOFT_ENTRA_CLIENT_SECRET none Entra client secret value
MICROSOFT_ENTRA_REDIRECT_URI http://localhost:3000/api/v1/auth/... Registered Web callback URI
QNE_SCHEDULER_APP_ID n3-daily-cfo Stable QNE scheduler app ID
QNE_SCHEDULER_APP_NAME Azure Project Log QNE scheduler display name
QNE_SCHEDULER_KEY_ID none Server-only QNE request key ID
QNE_SCHEDULER_SIGNING_SECRET none Server-only QNE HMAC secret
QNE_SCHEDULER_AZURE_DEVOPS_PAT none Server-only scheduled Azure DevOps PAT
QNE_SCHEDULER_DEFAULT_PROJECT_ID none Default project for unfiltered schedules

Frontend variables belong in apps/frontend/.env. Anything prefixed with VITE_ is bundled into browser code and must never contain credentials or secrets.

Planned phases

  1. Full-stack modular-monolith foundation (complete)
  2. PAT/Entra connections and Azure DevOps project retrieval (complete)
  3. Live Azure Boards task overview and contribution/deployment correlation (current)
  4. Account persistence and durable sessions started; project synchronization and caching remain
  5. Azure Project Log registration/login is complete; roles and application authorization policies remain
  6. Signed QuinnyAI script deployment receipts and historical release evidence
  7. Lightweight integrations (Craft, QNE Scheduler, and cloud Ask Taskie started), resilience, and deployment hardening

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages