Orpheus Systems Architecture

Contract Calendar: architecture

Generated from architecture/ on every change to main: the C4 views come from the Structurizr workspace, the documents from the TOGAF phases and the Architecture Decision Records. Built 2026-10-23 15:22 UTC.

C4 views

Landscape

Everything: the SPFx suite, the Copilot agent, provisioning, SharePoint Online and the Microsoft services around them. Proposed elements (ADR 0016, ADR 0018) are dashed.

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["System Landscape View"]
    style diagram fill:#ffffff,stroke:#ffffff

    1["<div style='font-weight: bold'>Contractor</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Consultant placed on one or<br />more procurement contracts.<br />Marks on-site and off-site<br />days, full or half, and<br />prints the timesheet. Books<br />days through the Copilot<br />agent too.</div>"]
    style 1 fill:#0b0d10,stroke:#07090b,color:#ffffff
    2["<div style='font-weight: bold'>Contract manager</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Manages at least one<br />contract, or is a member of<br />the Orpheus Contract Managers<br />group (Contribute). May<br />create contracts; edits only<br />the contracts they manage.<br />Watches used and remaining<br />days, generates the<br />timesheet. Read-only on<br />calendar days. Reads the<br />contracts they manage by<br />vendor through the agent,<br />read only.</div>"]
    style 2 fill:#0b0d10,stroke:#07090b,color:#ffffff
    3["<div style='font-weight: bold'>Site admin</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Admin role: a site collection<br />admin (IsSiteAdmin) or a<br />member of the associated<br />Owners group. Edits any<br />contract, maintains the<br />Holidays list, configures the<br />web part, runs the<br />provisioning scripts.</div>"]
    style 3 fill:#0b0d10,stroke:#07090b,color:#ffffff
    4["<div style='font-weight: bold'>HR</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Member of the SharePoint<br />group named by HR_GROUP<br />(default Orpheus HR, Read).<br />Exists only in the agent:<br />sees all contracts grouped by<br />vendor, read only; booking is<br />refused.</div>"]
    style 4 fill:#0b0d10,stroke:#07090b,color:#ffffff
    40("<div style='font-weight: bold'>Contract Calendar agent</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Microsoft 365 Copilot channel<br />to the same calendar. A<br />declarative agent whose only<br />action is a custom MCP<br />server. Contractors book and<br />remove days in natural<br />language, managers and HR<br />read contracts grouped by<br />vendor. Every tool call is<br />audited and runs as the<br />signed-in user.</div>")
    style 40 fill:#9dc3e6,stroke:#6d88a1,color:#0b0d10
    5["<div style='font-weight: bold'>Approving organisation</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>The client that signs the<br />printed timesheet. Never<br />named in code; a configurable<br />label with the default Client<br />approval.</div>"]
    style 5 fill:#7f7f7f,stroke:#585858,color:#ffffff
    59("<div style='font-weight: bold'>Provisioning scripts</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PnP PowerShell 3 on<br />PowerShell 7.4, run from the<br />host, idempotent with -WhatIf<br />(a re-run reports Unchanged<br />on every step), tested with<br />Pester against faked cmdlets.<br />No Node needed.</div>")
    style 59 fill:#e8db9c,stroke:#a2996d,color:#0b0d10
    6["<div style='font-weight: bold'>Owner / developer</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Orpheus Systems. Builds in<br />the Podman container,<br />packages the .sppkg files,<br />provisions sites, hosts the<br />agent demo, integrates<br />branches.</div>"]
    style 6 fill:#0b0d10,stroke:#07090b,color:#ffffff
    64("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
    style 64 fill:#7f7f7f,stroke:#585858,color:#ffffff
    7("<div style='font-weight: bold'>Contract Calendar suite</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>SharePoint Framework 1.23.2<br />solution: one web part, three<br />extensions and two shared<br />libraries. Runs entirely in<br />the browser inside SharePoint<br />Online pages; no server<br />component. Tracks on-site and<br />off-site days against<br />procurement contracts and<br />prints the timesheet PDF.</div>")
    style 7 fill:#e0a83a,stroke:#9c7528,color:#0b0d10
    72("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
    style 72 fill:#7f7f7f,stroke:#585858,color:#ffffff
    73("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
    style 73 fill:#7f7f7f,stroke:#585858,color:#ffffff
    74("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
    style 74 fill:#7f7f7f,stroke:#585858,color:#ffffff
    75("<div style='font-weight: bold'>Microsoft dev tunnel relay</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Persistent tunnel with a<br />stable host name, anonymous<br />at the relay because Copilot<br />cannot sign in to it. Demo<br />only, no SLA (ADR 0015).</div>")
    style 75 fill:#7f7f7f,stroke:#585858,color:#ffffff
    76("<div style='font-weight: bold'>GitHub</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the public repository.<br />GitHub Actions: ci.yml on<br />push and pull request to main<br />runs build.yml (hygiene scan,<br />build, typecheck, production<br />package and tests in<br />docker.io/m365pnp/spfx:1.23.2,<br />Pester on pwsh); release.yml<br />attaches the four .sppkg to<br />v* releases. Dependabot<br />updates the GitHub Actions<br />monthly. A ruleset protects<br />main. Never deploys to a<br />tenant.</div>")
    style 76 fill:#3a3a3a,stroke:#282828,color:#ffffff
    77("<div style='font-weight: bold'>PnP sharepoint-skills catalog</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED (ADR 0018). Source<br />of the three MIT-licensed<br />skills<br />(invoice-po-reconciliation,<br />project-health-check,<br />metadata-completeness-auditor)<br />that the oversight agent<br />adapts with attribution.</div>")
    style 77 fill:#7f7f7f,stroke:#585858,color:#ffffff
    78("<div style='font-weight: bold'>Graph store (GraphRAG)</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED, LATER (ADR 0018).<br />Only if questions need three<br />or more hops across documents<br />and relations. Neo4j by<br />default, PostgreSQL + AGE<br />only if PostgreSQL enters the<br />stack, Cosmos DB for Gremlin<br />excluded. Needs its own ADR.</div>")
    style 78 fill:#7f7f7f,stroke:#585858,color:#ffffff

    6-. "<div>Runs the mcp compose service<br />and hosts the dev tunnel for<br />the demo</div><div style='font-size: 70%'>[podman compose, devtunnel CLI]</div>" .->40
    6-. "<div>Runs the scripts against the<br />dev tenant</div><div style='font-size: 70%'>[PowerShell 7.4]</div>" .->59
    6-. "<div>Pushes branches and opens<br />pull requests to the<br />protected main</div><div style='font-size: 70%'>[git, HTTPS]</div>" .->76
    76-. "<div>Builds, tests and packages<br />the four .sppkg in the SPFx<br />container</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->7
    76-. "<div>Typechecks and tests the MCP<br />server</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->40
    76-. "<div>Runs the Pester tests against<br />faked PnP cmdlets</div><div style='font-size: 70%'>[GitHub Actions, pwsh]</div>" .->59
    7-. "<div>Shows the overlap dialog,<br />stats and toasts</div><div style='font-size: 70%'>[Browser]</div>" .->1
    7-. "<div>Downloads the generated<br />timesheet PDF</div><div style='font-size: 70%'>[Browser]</div>" .->2
    7-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->64
    72-. "<div>Runs the declarative agent;<br />reads tools and schemas from<br />the server at runtime<br />(dynamic discovery)</div><div style='font-size: 70%'></div>" .->40
    72-. "<div>Obtains the SSO token for the<br />server's Application ID URI</div><div style='font-size: 70%'>[OAuth 2.0]</div>" .->73
    72-. "<div>CopilotInteraction record per<br />prompt (layer 1, automatic)</div><div style='font-size: 70%'></div>" .->74
    64-. "<div>ListItem* records with the<br />real user as actor (layer 2,<br />automatic)</div><div style='font-size: 70%'></div>" .->74
    40-. "<div>Fetches signing keys (JWKS)<br />and validates issuer and<br />audience</div><div style='font-size: 70%'>[HTTPS]</div>" .->73
    40-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    40-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64
    77-. "<div>PROPOSED: three skills<br />adapted with attribution</div><div style='font-size: 70%'></div>" .->40
    40-. "<div>PROPOSED, LATER: GraphRAG<br />only when the trigger in ADR<br />0018 is met</div><div style='font-size: 70%'></div>" .->78
    2-. "<div>PROPOSED: asks for<br />reconciliation, health and<br />data audit; may save a<br />reconciliation</div><div style='font-size: 70%'></div>" .->40
    4-. "<div>PROPOSED: reads<br />reconciliation, health and<br />data audit</div><div style='font-size: 70%'></div>" .->40
    59-. "<div>Creates content type, list,<br />indexes, RemainingDays, list<br />validation; -SeedDemoData<br />upserts demo contracts</div><div style='font-size: 70%'>[PnP PowerShell, Invoke-PnPSiteTemplate]</div>" .->64
    59-. "<div>Registers the OrpheusChrome<br />web custom action once, with<br />list and page URLs in<br />ClientSideComponentProperties</div><div style='font-size: 70%'>[PnP PowerShell, REST UserCustomActions]</div>" .->7
    1-. "<div>Marks on-site and off-site<br />days, resolves overlap days,<br />generates the timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->7
    1-. "<div>Asks the Contract Calendar<br />agent to book, remove and<br />list days</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    1-. "<div>Hands over the signed<br />timesheet PDF</div><div style='font-size: 70%'>[Offline]</div>" .->5
    2-. "<div>Watches used and remaining<br />days read-only, generates the<br />timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->7
    2-. "<div>Asks the agent for contracts<br />grouped by vendor (read only)</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    3-. "<div>Edits any contract</div><div style='font-size: 70%'>[Browser]</div>" .->7
    3-. "<div>Maintains holidays in the<br />standard list UI</div><div style='font-size: 70%'>[Browser]</div>" .->64
    3-. "<div>Runs<br />Provision-OrpheusSite.ps1<br />-InstallSolutions and the<br />optional scripts</div><div style='font-size: 70%'>[PowerShell 7.4]</div>" .->59
    4-. "<div>Asks the agent for all<br />contracts grouped by vendor<br />(read only)</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    6-. "<div>Builds, tests and packages in<br />the Podman container</div><div style='font-size: 70%'>[podman compose, scripts/podman.ps1]</div>" .->7

  end

SuiteContext

System context of the SPFx suite (TOGAF Phase A, section 5).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["System Context View: Contract Calendar suite"]
    style diagram fill:#ffffff,stroke:#ffffff

    1["<div style='font-weight: bold'>Contractor</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Consultant placed on one or<br />more procurement contracts.<br />Marks on-site and off-site<br />days, full or half, and<br />prints the timesheet. Books<br />days through the Copilot<br />agent too.</div>"]
    style 1 fill:#0b0d10,stroke:#07090b,color:#ffffff
    2["<div style='font-weight: bold'>Contract manager</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Manages at least one<br />contract, or is a member of<br />the Orpheus Contract Managers<br />group (Contribute). May<br />create contracts; edits only<br />the contracts they manage.<br />Watches used and remaining<br />days, generates the<br />timesheet. Read-only on<br />calendar days. Reads the<br />contracts they manage by<br />vendor through the agent,<br />read only.</div>"]
    style 2 fill:#0b0d10,stroke:#07090b,color:#ffffff
    3["<div style='font-weight: bold'>Site admin</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Admin role: a site collection<br />admin (IsSiteAdmin) or a<br />member of the associated<br />Owners group. Edits any<br />contract, maintains the<br />Holidays list, configures the<br />web part, runs the<br />provisioning scripts.</div>"]
    style 3 fill:#0b0d10,stroke:#07090b,color:#ffffff
    40("<div style='font-weight: bold'>Contract Calendar agent</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Microsoft 365 Copilot channel<br />to the same calendar. A<br />declarative agent whose only<br />action is a custom MCP<br />server. Contractors book and<br />remove days in natural<br />language, managers and HR<br />read contracts grouped by<br />vendor. Every tool call is<br />audited and runs as the<br />signed-in user.</div>")
    style 40 fill:#9dc3e6,stroke:#6d88a1,color:#0b0d10
    59("<div style='font-weight: bold'>Provisioning scripts</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PnP PowerShell 3 on<br />PowerShell 7.4, run from the<br />host, idempotent with -WhatIf<br />(a re-run reports Unchanged<br />on every step), tested with<br />Pester against faked cmdlets.<br />No Node needed.</div>")
    style 59 fill:#e8db9c,stroke:#a2996d,color:#0b0d10
    6["<div style='font-weight: bold'>Owner / developer</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Orpheus Systems. Builds in<br />the Podman container,<br />packages the .sppkg files,<br />provisions sites, hosts the<br />agent demo, integrates<br />branches.</div>"]
    style 6 fill:#0b0d10,stroke:#07090b,color:#ffffff
    64("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
    style 64 fill:#7f7f7f,stroke:#585858,color:#ffffff
    7("<div style='font-weight: bold'>Contract Calendar suite</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>SharePoint Framework 1.23.2<br />solution: one web part, three<br />extensions and two shared<br />libraries. Runs entirely in<br />the browser inside SharePoint<br />Online pages; no server<br />component. Tracks on-site and<br />off-site days against<br />procurement contracts and<br />prints the timesheet PDF.</div>")
    style 7 fill:#e0a83a,stroke:#9c7528,color:#0b0d10
    76("<div style='font-weight: bold'>GitHub</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the public repository.<br />GitHub Actions: ci.yml on<br />push and pull request to main<br />runs build.yml (hygiene scan,<br />build, typecheck, production<br />package and tests in<br />docker.io/m365pnp/spfx:1.23.2,<br />Pester on pwsh); release.yml<br />attaches the four .sppkg to<br />v* releases. Dependabot<br />updates the GitHub Actions<br />monthly. A ruleset protects<br />main. Never deploys to a<br />tenant.</div>")
    style 76 fill:#3a3a3a,stroke:#282828,color:#ffffff

    6-. "<div>Runs the mcp compose service<br />and hosts the dev tunnel for<br />the demo</div><div style='font-size: 70%'>[podman compose, devtunnel CLI]</div>" .->40
    6-. "<div>Runs the scripts against the<br />dev tenant</div><div style='font-size: 70%'>[PowerShell 7.4]</div>" .->59
    6-. "<div>Pushes branches and opens<br />pull requests to the<br />protected main</div><div style='font-size: 70%'>[git, HTTPS]</div>" .->76
    76-. "<div>Builds, tests and packages<br />the four .sppkg in the SPFx<br />container</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->7
    76-. "<div>Typechecks and tests the MCP<br />server</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->40
    76-. "<div>Runs the Pester tests against<br />faked PnP cmdlets</div><div style='font-size: 70%'>[GitHub Actions, pwsh]</div>" .->59
    7-. "<div>Shows the overlap dialog,<br />stats and toasts</div><div style='font-size: 70%'>[Browser]</div>" .->1
    7-. "<div>Downloads the generated<br />timesheet PDF</div><div style='font-size: 70%'>[Browser]</div>" .->2
    7-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->64
    40-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    40-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64
    2-. "<div>PROPOSED: asks for<br />reconciliation, health and<br />data audit; may save a<br />reconciliation</div><div style='font-size: 70%'></div>" .->40
    59-. "<div>Creates content type, list,<br />indexes, RemainingDays, list<br />validation; -SeedDemoData<br />upserts demo contracts</div><div style='font-size: 70%'>[PnP PowerShell, Invoke-PnPSiteTemplate]</div>" .->64
    59-. "<div>Registers the OrpheusChrome<br />web custom action once, with<br />list and page URLs in<br />ClientSideComponentProperties</div><div style='font-size: 70%'>[PnP PowerShell, REST UserCustomActions]</div>" .->7
    1-. "<div>Marks on-site and off-site<br />days, resolves overlap days,<br />generates the timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->7
    2-. "<div>Watches used and remaining<br />days read-only, generates the<br />timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->7
    3-. "<div>Edits any contract</div><div style='font-size: 70%'>[Browser]</div>" .->7
    3-. "<div>Maintains holidays in the<br />standard list UI</div><div style='font-size: 70%'>[Browser]</div>" .->64
    3-. "<div>Runs<br />Provision-OrpheusSite.ps1<br />-InstallSolutions and the<br />optional scripts</div><div style='font-size: 70%'>[PowerShell 7.4]</div>" .->59
    6-. "<div>Builds, tests and packages in<br />the Podman container</div><div style='font-size: 70%'>[podman compose, scripts/podman.ps1]</div>" .->7

  end

AgentContext

System context of the Contract Calendar agent and the proposed oversight agent (ADR 0011 to ADR 0018).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["System Context View: Contract Calendar agent"]
    style diagram fill:#ffffff,stroke:#ffffff

    2["<div style='font-weight: bold'>Contract manager</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Manages at least one<br />contract, or is a member of<br />the Orpheus Contract Managers<br />group (Contribute). May<br />create contracts; edits only<br />the contracts they manage.<br />Watches used and remaining<br />days, generates the<br />timesheet. Read-only on<br />calendar days. Reads the<br />contracts they manage by<br />vendor through the agent,<br />read only.</div>"]
    style 2 fill:#0b0d10,stroke:#07090b,color:#ffffff
    4["<div style='font-weight: bold'>HR</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Member of the SharePoint<br />group named by HR_GROUP<br />(default Orpheus HR, Read).<br />Exists only in the agent:<br />sees all contracts grouped by<br />vendor, read only; booking is<br />refused.</div>"]
    style 4 fill:#0b0d10,stroke:#07090b,color:#ffffff
    40("<div style='font-weight: bold'>Contract Calendar agent</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Microsoft 365 Copilot channel<br />to the same calendar. A<br />declarative agent whose only<br />action is a custom MCP<br />server. Contractors book and<br />remove days in natural<br />language, managers and HR<br />read contracts grouped by<br />vendor. Every tool call is<br />audited and runs as the<br />signed-in user.</div>")
    style 40 fill:#9dc3e6,stroke:#6d88a1,color:#0b0d10
    6["<div style='font-weight: bold'>Owner / developer</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Orpheus Systems. Builds in<br />the Podman container,<br />packages the .sppkg files,<br />provisions sites, hosts the<br />agent demo, integrates<br />branches.</div>"]
    style 6 fill:#0b0d10,stroke:#07090b,color:#ffffff
    64("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
    style 64 fill:#7f7f7f,stroke:#585858,color:#ffffff
    7("<div style='font-weight: bold'>Contract Calendar suite</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>SharePoint Framework 1.23.2<br />solution: one web part, three<br />extensions and two shared<br />libraries. Runs entirely in<br />the browser inside SharePoint<br />Online pages; no server<br />component. Tracks on-site and<br />off-site days against<br />procurement contracts and<br />prints the timesheet PDF.</div>")
    style 7 fill:#e0a83a,stroke:#9c7528,color:#0b0d10
    72("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
    style 72 fill:#7f7f7f,stroke:#585858,color:#ffffff
    73("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
    style 73 fill:#7f7f7f,stroke:#585858,color:#ffffff
    76("<div style='font-weight: bold'>GitHub</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the public repository.<br />GitHub Actions: ci.yml on<br />push and pull request to main<br />runs build.yml (hygiene scan,<br />build, typecheck, production<br />package and tests in<br />docker.io/m365pnp/spfx:1.23.2,<br />Pester on pwsh); release.yml<br />attaches the four .sppkg to<br />v* releases. Dependabot<br />updates the GitHub Actions<br />monthly. A ruleset protects<br />main. Never deploys to a<br />tenant.</div>")
    style 76 fill:#3a3a3a,stroke:#282828,color:#ffffff
    77("<div style='font-weight: bold'>PnP sharepoint-skills catalog</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED (ADR 0018). Source<br />of the three MIT-licensed<br />skills<br />(invoice-po-reconciliation,<br />project-health-check,<br />metadata-completeness-auditor)<br />that the oversight agent<br />adapts with attribution.</div>")
    style 77 fill:#7f7f7f,stroke:#585858,color:#ffffff
    78("<div style='font-weight: bold'>Graph store (GraphRAG)</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED, LATER (ADR 0018).<br />Only if questions need three<br />or more hops across documents<br />and relations. Neo4j by<br />default, PostgreSQL + AGE<br />only if PostgreSQL enters the<br />stack, Cosmos DB for Gremlin<br />excluded. Needs its own ADR.</div>")
    style 78 fill:#7f7f7f,stroke:#585858,color:#ffffff

    6-. "<div>Runs the mcp compose service<br />and hosts the dev tunnel for<br />the demo</div><div style='font-size: 70%'>[podman compose, devtunnel CLI]</div>" .->40
    6-. "<div>Pushes branches and opens<br />pull requests to the<br />protected main</div><div style='font-size: 70%'>[git, HTTPS]</div>" .->76
    76-. "<div>Builds, tests and packages<br />the four .sppkg in the SPFx<br />container</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->7
    76-. "<div>Typechecks and tests the MCP<br />server</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->40
    7-. "<div>Downloads the generated<br />timesheet PDF</div><div style='font-size: 70%'>[Browser]</div>" .->2
    7-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->64
    72-. "<div>Runs the declarative agent;<br />reads tools and schemas from<br />the server at runtime<br />(dynamic discovery)</div><div style='font-size: 70%'></div>" .->40
    72-. "<div>Obtains the SSO token for the<br />server's Application ID URI</div><div style='font-size: 70%'>[OAuth 2.0]</div>" .->73
    40-. "<div>Fetches signing keys (JWKS)<br />and validates issuer and<br />audience</div><div style='font-size: 70%'>[HTTPS]</div>" .->73
    40-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    40-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64
    77-. "<div>PROPOSED: three skills<br />adapted with attribution</div><div style='font-size: 70%'></div>" .->40
    40-. "<div>PROPOSED, LATER: GraphRAG<br />only when the trigger in ADR<br />0018 is met</div><div style='font-size: 70%'></div>" .->78
    2-. "<div>PROPOSED: asks for<br />reconciliation, health and<br />data audit; may save a<br />reconciliation</div><div style='font-size: 70%'></div>" .->40
    4-. "<div>PROPOSED: reads<br />reconciliation, health and<br />data audit</div><div style='font-size: 70%'></div>" .->40
    2-. "<div>Watches used and remaining<br />days read-only, generates the<br />timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->7
    2-. "<div>Asks the agent for contracts<br />grouped by vendor (read only)</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    4-. "<div>Asks the agent for all<br />contracts grouped by vendor<br />(read only)</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    6-. "<div>Builds, tests and packages in<br />the Podman container</div><div style='font-size: 70%'>[podman compose, scripts/podman.ps1]</div>" .->7

  end

SuiteContainers

The web part, the three extensions, the two shared libraries and the SharePoint lists they read and write (TOGAF Phase C, application architecture section 3).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Container View: Contract Calendar suite"]
    style diagram fill:#ffffff,stroke:#ffffff

    1["<div style='font-weight: bold'>Contractor</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Consultant placed on one or<br />more procurement contracts.<br />Marks on-site and off-site<br />days, full or half, and<br />prints the timesheet. Books<br />days through the Copilot<br />agent too.</div>"]
    style 1 fill:#0b0d10,stroke:#07090b,color:#ffffff
    2["<div style='font-weight: bold'>Contract manager</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Manages at least one<br />contract, or is a member of<br />the Orpheus Contract Managers<br />group (Contribute). May<br />create contracts; edits only<br />the contracts they manage.<br />Watches used and remaining<br />days, generates the<br />timesheet. Read-only on<br />calendar days. Reads the<br />contracts they manage by<br />vendor through the agent,<br />read only.</div>"]
    style 2 fill:#0b0d10,stroke:#07090b,color:#ffffff
    3["<div style='font-weight: bold'>Site admin</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Admin role: a site collection<br />admin (IsSiteAdmin) or a<br />member of the associated<br />Owners group. Edits any<br />contract, maintains the<br />Holidays list, configures the<br />web part, runs the<br />provisioning scripts.</div>"]
    style 3 fill:#0b0d10,stroke:#07090b,color:#ffffff
    40("<div style='font-weight: bold'>Contract Calendar agent</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Microsoft 365 Copilot channel<br />to the same calendar. A<br />declarative agent whose only<br />action is a custom MCP<br />server. Contractors book and<br />remove days in natural<br />language, managers and HR<br />read contracts grouped by<br />vendor. Every tool call is<br />audited and runs as the<br />signed-in user.</div>")
    style 40 fill:#9dc3e6,stroke:#6d88a1,color:#0b0d10
    59("<div style='font-weight: bold'>Provisioning scripts</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PnP PowerShell 3 on<br />PowerShell 7.4, run from the<br />host, idempotent with -WhatIf<br />(a re-run reports Unchanged<br />on every step), tested with<br />Pester against faked cmdlets.<br />No Node needed.</div>")
    style 59 fill:#e8db9c,stroke:#a2996d,color:#0b0d10

    subgraph 7 ["Contract Calendar suite"]
      style 7 fill:#ffffff,stroke:#9c7528,color:#9c7528

      19["<div style='font-weight: bold'>shared-ui</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, React 17, pdfmake, html-to-pdfmake 2.5, npm workspace @orpheussystems/shared-ui]</div><div style='font-size: 80%; margin-top:10px'>Design tokens and brand, the<br />shared toast stack, the<br />timesheet model and the PDF<br />builders. The PDF code loads<br />in a lazy chunk<br />(orpheus-timesheet-pdf).</div>"]
      style 19 fill:#5c5c5c,stroke:#404040,color:#ffffff
      26("<div style='font-weight: bold'>wp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx web part, React 17, Fluent UI 8, npm workspace @orpheussystems/wp-contract-calendar]</div><div style='font-size: 80%; margin-top:10px'>The contract calendar web<br />part: contract selector,<br />month grids for the contract<br />period, selection type<br />picker, range drag, stats<br />panel, overlap dialog and the<br />timesheet dialog. Runs on<br />demo data in a debug build.</div>")
      style 26 fill:#b8862e,stroke:#805d20,color:#0b0d10
      37("<div style='font-weight: bold'>ext-app-customizer</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx application customizer, React 17, npm workspace @orpheussystems/ext-app-customizer]</div><div style='font-size: 80%; margin-top:10px'>Header and footer on every<br />page: Orpheus mark and<br />wordmark, role-aware menu (My<br />calendar, Contracts,<br />Holidays) that collapses<br />below 640 px, footer with<br />contact and solution version.<br />Menu items and list URLs come<br />from<br />ClientSideComponentProperties<br />written by provisioning. The<br />package has no feature; the<br />OrpheusChrome web custom<br />action is registered by<br />OrpheusSolutions.psm1. A<br />failed role check shows a<br />menu without role items.</div>")
      style 37 fill:#b8862e,stroke:#805d20,color:#0b0d10
      38("<div style='font-weight: bold'>ext-field-customizer</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx field customizer, React 17, npm workspace @orpheussystems/ext-field-customizer]</div><div style='font-size: 80%; margin-top:10px'>Remaining-days bars on the<br />RemainingDays column of the<br />Contracts list: on-site and<br />off-site used against target,<br />amber at 80 percent, red when<br />over. Reads only the row's<br />own columns; no extra query<br />per row.</div>")
      style 38 fill:#b8862e,stroke:#805d20,color:#0b0d10
      39("<div style='font-weight: bold'>ext-form-customizer</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx form customizer, React 17, Fluent UI 8, npm workspace @orpheussystems/ext-form-customizer]</div><div style='font-size: 80%; margin-top:10px'>New, Edit and Display forms<br />for the Contracts list:<br />validation as you type,<br />people and date pickers,<br />overlap warning, Open<br />calendar link. New: Admin or<br />ContractManager. Edit: Admin<br />or the contract's own<br />manager. If the role check<br />fails, nobody edits (fail<br />closed). When an edit changes<br />the period, it recomputes the<br />used days from the entries<br />inside the new period in the<br />same save and warns about<br />booked days outside it.</div>")
      style 39 fill:#b8862e,stroke:#805d20,color:#0b0d10
      8["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
      style 8 fill:#5c5c5c,stroke:#404040,color:#ffffff
    end

    26-. "<div>Creates when the environment<br />is Local or useLocalData is<br />on</div><div style='font-size: 70%'></div>" .->8
    26-. "<div>Shows the overlap dialog,<br />stats and toasts</div><div style='font-size: 70%'>[Browser]</div>" .->1
    19-. "<div>Downloads the generated<br />timesheet PDF</div><div style='font-size: 70%'>[Browser]</div>" .->2
    26-. "<div>Builds the timesheet model</div><div style='font-size: 70%'></div>" .->19
    19-. "<div>Totals and hours per day</div><div style='font-size: 70%'></div>" .->8
    37-. "<div>Role derivation<br />(deriveRolesForContracts,<br />resolveListIdByUrl), menu<br />link helpers</div><div style='font-size: 70%'>[TypeScript import]</div>" .->8
    37-. "<div>Theme tokens, brand, toasts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->19
    38-. "<div>Theme tokens</div><div style='font-size: 70%'>[TypeScript import]</div>" .->19
    39-. "<div>Models, mappers, overlap<br />query,<br />deriveRolesForContracts<br />(fails closed)</div><div style='font-size: 70%'>[TypeScript import]</div>" .->8
    39-. "<div>Theme tokens, toasts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->19
    40-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->8
    2-. "<div>PROPOSED: asks for<br />reconciliation, health and<br />data audit; may save a<br />reconciliation</div><div style='font-size: 70%'></div>" .->40
    59-. "<div>Registers the OrpheusChrome<br />web custom action once, with<br />list and page URLs in<br />ClientSideComponentProperties</div><div style='font-size: 70%'>[PnP PowerShell, REST UserCustomActions]</div>" .->37
    59-. "<div>Binds to<br />Contracts.RemainingDays and<br />adds the column to the<br />default view</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->38
    59-. "<div>Binds to the New, Edit and<br />Display forms of Orpheus<br />Contract</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->39
    1-. "<div>Marks on-site and off-site<br />days, resolves overlap days,<br />generates the timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->26
    1-. "<div>Sees the header menu (My<br />calendar)</div><div style='font-size: 70%'>[Browser]</div>" .->37
    2-. "<div>Watches used and remaining<br />days read-only, generates the<br />timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->26
    2-. "<div>Creates and edits the<br />contracts they manage</div><div style='font-size: 70%'>[Browser]</div>" .->39
    2-. "<div>Reads the remaining-days bars<br />in the Contracts list views</div><div style='font-size: 70%'>[Browser]</div>" .->38
    2-. "<div>Sees the header menu<br />(Contracts)</div><div style='font-size: 70%'>[Browser]</div>" .->37
    3-. "<div>Edits any contract</div><div style='font-size: 70%'>[Browser]</div>" .->39
    3-. "<div>Configures the web part:<br />lists, labels, template path</div><div style='font-size: 70%'>[Browser, property pane]</div>" .->26
    3-. "<div>Runs<br />Provision-OrpheusSite.ps1<br />-InstallSolutions and the<br />optional scripts</div><div style='font-size: 70%'>[PowerShell 7.4]</div>" .->59

  end

AgentContainers

Declarative agent, MCP server, audit sink, and the proposed oversight agent on the same server.

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Container View: Contract Calendar agent"]
    style diagram fill:#ffffff,stroke:#ffffff

    2["<div style='font-weight: bold'>Contract manager</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Manages at least one<br />contract, or is a member of<br />the Orpheus Contract Managers<br />group (Contribute). May<br />create contracts; edits only<br />the contracts they manage.<br />Watches used and remaining<br />days, generates the<br />timesheet. Read-only on<br />calendar days. Reads the<br />contracts they manage by<br />vendor through the agent,<br />read only.</div>"]
    style 2 fill:#0b0d10,stroke:#07090b,color:#ffffff
    4["<div style='font-weight: bold'>HR</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Member of the SharePoint<br />group named by HR_GROUP<br />(default Orpheus HR, Read).<br />Exists only in the agent:<br />sees all contracts grouped by<br />vendor, read only; booking is<br />refused.</div>"]
    style 4 fill:#0b0d10,stroke:#07090b,color:#ffffff
    6["<div style='font-weight: bold'>Owner / developer</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Orpheus Systems. Builds in<br />the Podman container,<br />packages the .sppkg files,<br />provisions sites, hosts the<br />agent demo, integrates<br />branches.</div>"]
    style 6 fill:#0b0d10,stroke:#07090b,color:#ffffff
    7("<div style='font-weight: bold'>Contract Calendar suite</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>SharePoint Framework 1.23.2<br />solution: one web part, three<br />extensions and two shared<br />libraries. Runs entirely in<br />the browser inside SharePoint<br />Online pages; no server<br />component. Tracks on-site and<br />off-site days against<br />procurement contracts and<br />prints the timesheet PDF.</div>")
    style 7 fill:#e0a83a,stroke:#9c7528,color:#0b0d10
    72("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
    style 72 fill:#7f7f7f,stroke:#585858,color:#ffffff
    73("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
    style 73 fill:#7f7f7f,stroke:#585858,color:#ffffff
    76("<div style='font-weight: bold'>GitHub</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the public repository.<br />GitHub Actions: ci.yml on<br />push and pull request to main<br />runs build.yml (hygiene scan,<br />build, typecheck, production<br />package and tests in<br />docker.io/m365pnp/spfx:1.23.2,<br />Pester on pwsh); release.yml<br />attaches the four .sppkg to<br />v* releases. Dependabot<br />updates the GitHub Actions<br />monthly. A ruleset protects<br />main. Never deploys to a<br />tenant.</div>")
    style 76 fill:#3a3a3a,stroke:#282828,color:#ffffff
    77("<div style='font-weight: bold'>PnP sharepoint-skills catalog</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED (ADR 0018). Source<br />of the three MIT-licensed<br />skills<br />(invoice-po-reconciliation,<br />project-health-check,<br />metadata-completeness-auditor)<br />that the oversight agent<br />adapts with attribution.</div>")
    style 77 fill:#7f7f7f,stroke:#585858,color:#ffffff
    78("<div style='font-weight: bold'>Graph store (GraphRAG)</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED, LATER (ADR 0018).<br />Only if questions need three<br />or more hops across documents<br />and relations. Neo4j by<br />default, PostgreSQL + AGE<br />only if PostgreSQL enters the<br />stack, Cosmos DB for Gremlin<br />excluded. Needs its own ADR.</div>")
    style 78 fill:#7f7f7f,stroke:#585858,color:#ffffff

    subgraph 40 ["Contract Calendar agent"]
      style 40 fill:#ffffff,stroke:#6d88a1,color:#6d88a1

      41("<div style='font-weight: bold'>agent-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, JSON manifests]</div><div style='font-size: 80%; margin-top:10px'>Declarative agent package:<br />app manifest 1.30,<br />declarative agent manifest<br />1.8, instruction.txt and one<br />RemoteMCPServer plugin<br />(manifest 2.4) with dynamic<br />tool discovery and Entra SSO<br />(OAuthPluginVault). The<br />instructions require one<br />confirmation before book_days<br />and remove_days. No code, no<br />build; provisioned by the<br />Agents Toolkit.</div>")
      style 41 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
      42["<div style='font-weight: bold'>mcp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Node 22, TypeScript 5.8, Express 5, MCP TypeScript SDK 1.x, zod, jose 6, msal-node 3, PnPjs 4 with @pnp/nodejs]</div><div style='font-size: 80%; margin-top:10px'>MCP server, the agent's<br />action layer. Stateless<br />Streamable HTTP on port 8787:<br />POST /mcp, GET /healthz.<br />Validates the Entra SSO<br />token, exchanges it on behalf<br />of the user for a delegated<br />SharePoint token<br />(AllSites.Write), derives<br />roles including HR, enforces<br />visibility and the<br />contractor-only write rule,<br />writes one JSON audit line<br />per tool call. Reuses<br />shared-core unchanged.</div>"]
      style 42 fill:#2f6fa3,stroke:#204d72,color:#ffffff
      57[("<div style='font-weight: bold'>Audit sink</div><div style='font-size: 70%; margin-top: 0px'>[Container: JSON lines; Log Analytics in the target]</div><div style='font-size: 80%; margin-top:10px'>Where the per-tool-call JSON<br />lines land: stdout of the mcp<br />compose service or the file<br />in AUDIT_LOG_PATH during the<br />demo; a dedicated Log<br />Analytics table in the Azure<br />target (ADR 0014, ADR 0016).</div>")]
      style 57 fill:#4d4d4d,stroke:#353535,color:#ffffff
      58("<div style='font-weight: bold'>agent-contract-oversight</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, planned]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED (ADR 0018). Second<br />declarative agent for<br />contract managers, HR and<br />site admins: reconciliation<br />of booked days against the<br />contract, contract health<br />check and contract data<br />audit, adapted from three<br />MIT-licensed PnP<br />sharepoint-skills. Two<br />package flavours: manifest<br />1.9 with agent_skills<br />(Frontier preview) and<br />manifest 1.8 plain. Never<br />touches calendar entries.</div>")
      style 58 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
    end

    6-. "<div>Runs the mcp compose service<br />and hosts the dev tunnel for<br />the demo</div><div style='font-size: 70%'>[podman compose, devtunnel CLI]</div>" .->42
    6-. "<div>Provisions and sideloads the<br />agent package</div><div style='font-size: 70%'>[Microsoft 365 Agents Toolkit]</div>" .->41
    6-. "<div>Pushes branches and opens<br />pull requests to the<br />protected main</div><div style='font-size: 70%'>[git, HTTPS]</div>" .->76
    76-. "<div>Builds, tests and packages<br />the four .sppkg in the SPFx<br />container</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->7
    76-. "<div>Typechecks and tests the MCP<br />server</div><div style='font-size: 70%'>[GitHub Actions]</div>" .->42
    7-. "<div>Downloads the generated<br />timesheet PDF</div><div style='font-size: 70%'>[Browser]</div>" .->2
    72-. "<div>Runs the declarative agent;<br />reads tools and schemas from<br />the server at runtime<br />(dynamic discovery)</div><div style='font-size: 70%'></div>" .->41
    41-. "<div>MCP over HTTPS, stateless<br />Streamable HTTP POST /mcp,<br />Entra SSO bearer token</div><div style='font-size: 70%'>[HTTPS, JSON-RPC]</div>" .->42
    72-. "<div>Obtains the SSO token for the<br />server's Application ID URI</div><div style='font-size: 70%'>[OAuth 2.0]</div>" .->73
    42-. "<div>Fetches signing keys (JWKS)<br />and validates issuer and<br />audience</div><div style='font-size: 70%'>[HTTPS]</div>" .->73
    42-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    42-. "<div>Appends one JSON line per<br />tool call</div><div style='font-size: 70%'></div>" .->57
    58-. "<div>PROPOSED (ADR 0018): MCP over<br />HTTPS to /mcp/oversight,<br />Entra SSO</div><div style='font-size: 70%'>[HTTPS, JSON-RPC]</div>" .->42
    72-. "<div>PROPOSED (ADR 0018): runs the<br />oversight agent</div><div style='font-size: 70%'></div>" .->58
    77-. "<div>PROPOSED: three skills<br />adapted with attribution</div><div style='font-size: 70%'></div>" .->58
    42-. "<div>PROPOSED, LATER: GraphRAG<br />only when the trigger in ADR<br />0018 is met</div><div style='font-size: 70%'></div>" .->78
    2-. "<div>PROPOSED: asks for<br />reconciliation, health and<br />data audit; may save a<br />reconciliation</div><div style='font-size: 70%'></div>" .->58
    4-. "<div>PROPOSED: reads<br />reconciliation, health and<br />data audit</div><div style='font-size: 70%'></div>" .->58
    2-. "<div>Watches used and remaining<br />days read-only, generates the<br />timesheet</div><div style='font-size: 70%'>[Browser]</div>" .->7
    2-. "<div>Asks the agent for contracts<br />grouped by vendor (read only)</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    4-. "<div>Asks the agent for all<br />contracts grouped by vendor<br />(read only)</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    6-. "<div>Builds, tests and packages in<br />the Podman container</div><div style='font-size: 70%'>[podman compose, scripts/podman.ps1]</div>" .->7

  end

SharePointSite

What the provisioning scripts create on the site and who reads or writes each list.

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Container View: SharePoint Online"]
    style diagram fill:#ffffff,stroke:#ffffff

    3["<div style='font-weight: bold'>Site admin</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Admin role: a site collection<br />admin (IsSiteAdmin) or a<br />member of the associated<br />Owners group. Edits any<br />contract, maintains the<br />Holidays list, configures the<br />web part, runs the<br />provisioning scripts.</div>"]
    style 3 fill:#0b0d10,stroke:#07090b,color:#ffffff
    7("<div style='font-weight: bold'>Contract Calendar suite</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>SharePoint Framework 1.23.2<br />solution: one web part, three<br />extensions and two shared<br />libraries. Runs entirely in<br />the browser inside SharePoint<br />Online pages; no server<br />component. Tracks on-site and<br />off-site days against<br />procurement contracts and<br />prints the timesheet PDF.</div>")
    style 7 fill:#e0a83a,stroke:#9c7528,color:#0b0d10
    40("<div style='font-weight: bold'>Contract Calendar agent</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Microsoft 365 Copilot channel<br />to the same calendar. A<br />declarative agent whose only<br />action is a custom MCP<br />server. Contractors book and<br />remove days in natural<br />language, managers and HR<br />read contracts grouped by<br />vendor. Every tool call is<br />audited and runs as the<br />signed-in user.</div>")
    style 40 fill:#9dc3e6,stroke:#6d88a1,color:#0b0d10
    59("<div style='font-weight: bold'>Provisioning scripts</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PnP PowerShell 3 on<br />PowerShell 7.4, run from the<br />host, idempotent with -WhatIf<br />(a re-run reports Unchanged<br />on every step), tested with<br />Pester against faked cmdlets.<br />No Node needed.</div>")
    style 59 fill:#e8db9c,stroke:#a2996d,color:#0b0d10
    74("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
    style 74 fill:#7f7f7f,stroke:#585858,color:#ffffff

    subgraph 64 ["SharePoint Online"]
      style 64 fill:#ffffff,stroke:#585858,color:#585858

      65[("<div style='font-weight: bold'>Contracts list</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint list]</div><div style='font-size: 80%; margin-top:10px'>Orpheus Contract content<br />type. Code, framework<br />contract, modality,<br />contractor, manager, period,<br />on-site and off-site targets,<br />hours per day, country,<br />status, used days kept by the<br />service, RemainingDays (field<br />customizer target), optional<br />template columns and Vendor.<br />Site-default permissions:<br />contractors hold Contribute<br />(ADR 0005).</div>")]
      style 65 fill:#7f7f7f,stroke:#585858,color:#ffffff
      66[("<div style='font-weight: bold'>CalendarEntries list</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint list]</div><div style='font-size: 80%; margin-top:10px'>One item per contractor per<br />date across all their<br />contracts (ADR 0008): lookup<br />to the contract, EntryDate<br />(indexed), EntryType<br />(OnSiteFull, OnSiteHalf,<br />OffSiteFull, OffSiteHalf),<br />optional note.</div>")]
      style 66 fill:#7f7f7f,stroke:#585858,color:#ffffff
      67[("<div style='font-weight: bold'>Holidays list</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint list]</div><div style='font-size: 80%; margin-top:10px'>Public holidays per country<br />(ISO alpha-2 or * for all),<br />Source Manual or Agent<br />(reserved for the future<br />holiday agent).</div>")]
      style 67 fill:#7f7f7f,stroke:#585858,color:#ffffff
      68("<div style='font-weight: bold'>ContractCalendar.aspx</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint modern page]</div><div style='font-size: 80%; margin-top:10px'>Modern page created by<br />provisioning that hosts the<br />web part with the three lists<br />preselected; honours the<br />contractId query parameter.<br />Also the place for a Button<br />or Quick links web part that<br />opens the agent's share link<br />(ADR 0017).</div>")
      style 68 fill:#7f7f7f,stroke:#585858,color:#ffffff
      69[("<div style='font-weight: bold'>Timesheet HTML template</div><div style='font-size: 70%; margin-top: 0px'>[Container: File in a document library]</div><div style='font-size: 80%; margin-top:10px'>Optional .html file uploaded<br />by a site owner to a library<br />only owners can edit; the web<br />part property points at it<br />(ADR 0010). Read with the<br />viewer's permissions on every<br />generation.</div>")]
      style 69 fill:#7f7f7f,stroke:#585858,color:#ffffff
      70("<div style='font-weight: bold'>Site collection App Catalog</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint App Catalog]</div><div style='font-size: 80%; margin-top:10px'>Where the four .sppkg<br />solutions are installed from.<br />Created once per site by a<br />SharePoint administrator;<br />filled by<br />OrpheusSolutions.psm1.</div>")
      style 70 fill:#7f7f7f,stroke:#585858,color:#ffffff
      71("<div style='font-weight: bold'>Site groups</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint groups]</div><div style='font-size: 80%; margin-top:10px'>Associated Owners group<br />(Admin role, together with<br />site collection admins),<br />Orpheus Contract Managers<br />with Contribute<br />(ContractManager role, may<br />create contracts; created by<br />provisioning, members kept by<br />the owner) and the HR group<br />named by HR_GROUP, default<br />Orpheus HR with Read<br />(agent-only HR role).</div>")
      style 71 fill:#7f7f7f,stroke:#585858,color:#ffffff
    end

    7-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->69
    7-. "<div>Reads role-filtered<br />contracts, updates used days</div><div style='font-size: 70%'>[PnPjs 4 REST, OData on indexed columns]</div>" .->65
    7-. "<div>Reads entries by contract and<br />period, upserts and deletes,<br />moves an entry between<br />contracts</div><div style='font-size: 70%'>[PnPjs 4 REST, batched writes]</div>" .->66
    7-. "<div>Reads holidays for the<br />contract's country and *</div><div style='font-size: 70%'>[PnPjs 4 REST]</div>" .->67
    7-. "<div>Reads IsSiteAdmin, the<br />associated Owners group and<br />the user's groups (Orpheus<br />Contract Managers) for the<br />roles; fails closed</div><div style='font-size: 70%'>[PnPjs 4 REST]</div>" .->71
    7-. "<div>Links to the calendar page</div><div style='font-size: 70%'></div>" .->68
    66-. "<div>ListItemCreated / Updated /<br />Deleted / Recycled with the<br />real user as actor (layer 2,<br />automatic)</div><div style='font-size: 70%'></div>" .->74
    65-. "<div>ListItemUpdated for used-days<br />and reconciliation writes<br />(layer 2, automatic)</div><div style='font-size: 70%'></div>" .->74
    40-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    40-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->71
    40-. "<div>OData pre-filter plus<br />in-memory re-check of every<br />item; Vendor column</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->65
    59-. "<div>Creates content type, list,<br />indexes, RemainingDays, list<br />validation; -SeedDemoData<br />upserts demo contracts</div><div style='font-size: 70%'>[PnP PowerShell, Invoke-PnPSiteTemplate]</div>" .->65
    59-. "<div>Creates with indexes;<br />-SeedDemoData upserts demo<br />entries</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->66
    59-. "<div>Creates; -SeedDemoData seeds<br />the holidays of -Country and<br />*</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->67
    59-. "<div>Installs or updates the four<br />.sppkg (-Scope Site); clean<br />reinstall when a manifest<br />stays empty</div><div style='font-size: 70%'>[PnP PowerShell, Add-PnPApp / Install-PnPApp]</div>" .->70
    59-. "<div>Registers the OrpheusChrome<br />web custom action once, with<br />list and page URLs in<br />ClientSideComponentProperties</div><div style='font-size: 70%'>[PnP PowerShell, REST UserCustomActions]</div>" .->7
    59-. "<div>Creates Orpheus Contract<br />Managers with Contribute when<br />missing</div><div style='font-size: 70%'>[PnP PowerShell, New-PnPSiteGroup]</div>" .->71
    59-. "<div>Creates or updates<br />SitePages/ContractCalendar.aspx<br />with the web part wired to<br />the three lists</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->68
    3-. "<div>Edits any contract</div><div style='font-size: 70%'>[Browser]</div>" .->7
    3-. "<div>Maintains holidays in the<br />standard list UI</div><div style='font-size: 70%'>[Browser]</div>" .->67
    3-. "<div>Uploads and edits the<br />timesheet HTML template</div><div style='font-size: 70%'>[Browser]</div>" .->69
    3-. "<div>Has a SharePoint<br />administrator create it once;<br />inspects the installed apps</div><div style='font-size: 70%'>[Browser]</div>" .->70
    3-. "<div>Runs<br />Provision-OrpheusSite.ps1<br />-InstallSolutions and the<br />optional scripts</div><div style='font-size: 70%'>[PowerShell 7.4]</div>" .->59

  end

SharedCoreComponents

shared-core: the IDataService seam, its two implementations and the rules every channel shares (ADR 0003, ADR 0008).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Component View: Contract Calendar suite - shared-core"]
    style diagram fill:#ffffff,stroke:#ffffff

    40("<div style='font-weight: bold'>Contract Calendar agent</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Microsoft 365 Copilot channel<br />to the same calendar. A<br />declarative agent whose only<br />action is a custom MCP<br />server. Contractors book and<br />remove days in natural<br />language, managers and HR<br />read contracts grouped by<br />vendor. Every tool call is<br />audited and runs as the<br />signed-in user.</div>")
    style 40 fill:#9dc3e6,stroke:#6d88a1,color:#0b0d10
    64("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
    style 64 fill:#7f7f7f,stroke:#585858,color:#ffffff

    subgraph 7 ["Contract Calendar suite"]
      style 7 fill:#ffffff,stroke:#9c7528,color:#9c7528

      subgraph 8 ["shared-core"]
        style 8 fill:#ffffff,stroke:#404040,color:#404040

        10["<div style='font-weight: bold'>rules</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/rules.ts]</div><div style='font-size: 80%; margin-top:10px'>Business rules: holiday<br />blocking (always) and weekend<br />blocking (blockWeekends,<br />default on), period checks,<br />overlap detection, used-days<br />totals of the entries inside<br />the contract period only<br />(computeUsedTotals), full = 1<br />and half = 0.5,<br />canCreateContract (Admin or<br />ContractManager),<br />canEditContract (Admin or the<br />contract's own manager),<br />CONTRACT_MANAGERS_GROUP.</div>"]
        style 10 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        11["<div style='font-weight: bold'>dates</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/dates.ts]</div><div style='font-size: 80%; margin-top:10px'>DateKey (yyyy-MM-dd) helpers,<br />Monday-first month<br />arithmetic, range iteration.</div>"]
        style 11 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        12["<div style='font-weight: bold'>links</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/links.ts]</div><div style='font-size: 80%; margin-top:10px'>Deep links to the calendar<br />page with the contractId<br />query parameter.</div>"]
        style 12 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        13["<div style='font-weight: bold'>templateData</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/templateData.ts]</div><div style='font-size: 80%; margin-top:10px'>ContractTemplateData: the<br />five optional template<br />columns and demo values.</div>"]
        style 13 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        14["<div style='font-weight: bold'>IDataService</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript interface, src/data/IDataService.ts]</div><div style='font-size: 80%; margin-top:10px'>The one data access seam:<br />getMyContracts<br />(role-filtered), getEntries,<br />upsertEntry, deleteEntry,<br />getHolidays, getCurrentUser.</div>"]
        style 14 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        15["<div style='font-weight: bold'>LocalDataService</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/data/LocalDataService.ts]</div><div style='font-size: 80%; margin-top:10px'>In-memory implementation over<br />bundled JSON fixtures (bridge<br />contracts, half days, BG and<br />* holidays, demo users). Used<br />by the workbench, the unit<br />tests and the MCP server in<br />DATA_MODE=local.</div>"]
        style 15 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        16["<div style='font-weight: bold'>SharePointDataService</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/data/SharePointDataService.ts, PnPjs 4]</div><div style='font-size: 80%; margin-top:10px'>PnPjs v4 implementation<br />against the three lists:<br />CAML/OData filters on indexed<br />columns, batched writes,<br />moves an entry that exists on<br />another contract of the same<br />contractor, recomputes<br />OnSiteUsedDays and<br />OffSiteUsedDays.</div>"]
        style 16 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        17["<div style='font-weight: bold'>spQueries / spMappers / listLookup</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/data/*.ts]</div><div style='font-size: 80%; margin-top:10px'>OData query builders, SP item<br />to model mappers<br />(CONTRACT_SELECT,<br />CONTRACT_EXPAND), list id<br />resolution by URL and<br />deriveRolesForContracts:<br />Contractor and<br />ContractManager from the<br />contracts, ContractManager<br />also from Orpheus Contract<br />Managers membership, Admin<br />from IsSiteAdmin or the<br />associated Owners group. Any<br />failed read rejects, so no<br />role is granted (fail<br />closed).</div>"]
        style 17 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        18["<div style='font-weight: bold'>templateDataQuery</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/data/templateDataQuery.ts]</div><div style='font-size: 80%; margin-top:10px'>getContractTemplateData:<br />reads the five optional<br />template columns of one<br />contract tolerantly.</div>"]
        style 18 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        9["<div style='font-weight: bold'>models</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/models.ts]</div><div style='font-size: 80%; margin-top:10px'>Contract, CalendarEntry,<br />Holiday, UserInfo, Role,<br />EntryType and the other<br />domain types.</div>"]
        style 9 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
      end

      19["<div style='font-weight: bold'>shared-ui</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, React 17, pdfmake, html-to-pdfmake 2.5, npm workspace @orpheussystems/shared-ui]</div><div style='font-size: 80%; margin-top:10px'>Design tokens and brand, the<br />shared toast stack, the<br />timesheet model and the PDF<br />builders. The PDF code loads<br />in a lazy chunk<br />(orpheus-timesheet-pdf).</div>"]
      style 19 fill:#5c5c5c,stroke:#404040,color:#ffffff
      26("<div style='font-weight: bold'>wp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx web part, React 17, Fluent UI 8, npm workspace @orpheussystems/wp-contract-calendar]</div><div style='font-size: 80%; margin-top:10px'>The contract calendar web<br />part: contract selector,<br />month grids for the contract<br />period, selection type<br />picker, range drag, stats<br />panel, overlap dialog and the<br />timesheet dialog. Runs on<br />demo data in a debug build.</div>")
      style 26 fill:#b8862e,stroke:#805d20,color:#0b0d10
    end

    26-. "<div>Creates when the environment<br />is Local or useLocalData is<br />on</div><div style='font-size: 70%'></div>" .->15
    26-. "<div>Creates otherwise, with the<br />lists chosen in the property<br />pane</div><div style='font-size: 70%'></div>" .->16
    26-. "<div>Exposes</div><div style='font-size: 70%'></div>" .->14
    26-. "<div>Applies blocking, toggle and<br />totals rules</div><div style='font-size: 70%'></div>" .->10
    26-. "<div>Date arithmetic</div><div style='font-size: 70%'></div>" .->11
    26-. "<div>Builds the timesheet model</div><div style='font-size: 70%'></div>" .->19
    26-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->64
    26-. "<div>Reads the template columns of<br />the contract</div><div style='font-size: 70%'></div>" .->18
    19-. "<div>Totals and hours per day</div><div style='font-size: 70%'></div>" .->10
    19-. "<div>Types</div><div style='font-size: 70%'></div>" .->9
    15-. "<div>Implements</div><div style='font-size: 70%'></div>" .->14
    16-. "<div>Implements</div><div style='font-size: 70%'></div>" .->14
    16-. "<div>Queries and mappers</div><div style='font-size: 70%'></div>" .->17
    16-. "<div>Enforces one entry per<br />contractor per date, blocked<br />days, used-days recomputation</div><div style='font-size: 70%'></div>" .->10
    16-. "<div>Reads role-filtered<br />contracts, updates used days</div><div style='font-size: 70%'>[PnPjs 4 REST, OData on indexed columns]</div>" .->64
    17-. "<div>Reads IsSiteAdmin, the<br />associated Owners group and<br />the user's groups (Orpheus<br />Contract Managers) for the<br />roles; fails closed</div><div style='font-size: 70%'>[PnPjs 4 REST]</div>" .->64
    18-. "<div>Reads one contract's template<br />columns tolerantly</div><div style='font-size: 70%'>[PnPjs 4 REST]</div>" .->64
    40-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->17
    40-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64
    40-. "<div>Creates per request with the<br />OBO token<br />(DATA_MODE=sharepoint)</div><div style='font-size: 70%'>[TypeScript import]</div>" .->16
    40-. "<div>Uses fixtures in<br />DATA_MODE=local and in tests</div><div style='font-size: 70%'>[TypeScript import]</div>" .->15
    40-. "<div>book_days and remove_days go<br />through upsertEntry and<br />deleteEntry; get_calendar and<br />get_holidays through the<br />reads</div><div style='font-size: 70%'>[TypeScript import]</div>" .->14
    40-. "<div>Business days, blocked days,<br />overlap detection</div><div style='font-size: 70%'>[TypeScript import]</div>" .->10

  end

SharedUiComponents

shared-ui: tokens, toasts and the two PDF paths (ADR 0004, ADR 0010).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Component View: Contract Calendar suite - shared-ui"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 7 ["Contract Calendar suite"]
      style 7 fill:#ffffff,stroke:#9c7528,color:#9c7528

      subgraph 19 ["shared-ui"]
        style 19 fill:#ffffff,stroke:#404040,color:#404040

        20["<div style='font-weight: bold'>theme tokens and brand</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/theme, src/brand]</div><div style='font-size: 80%; margin-top:10px'>Orpheus palette (amber<br />#E0A83A, dark #0B0D10), font<br />stack, brand strings and the<br />attribution line.</div>"]
        style 20 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        21["<div style='font-weight: bold'>toast</div><div style='font-size: 70%; margin-top: 0px'>[Component: React 17, src/toast]</div><div style='font-size: 80%; margin-top:10px'>One toast store and ToastHost<br />shared by every component on<br />the page.</div>"]
        style 21 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        22["<div style='font-weight: bold'>timesheetModel</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/pdf/timesheetModel.ts]</div><div style='font-size: 80%; margin-top:10px'>Builds the timesheet model<br />from contract, entries and<br />holidays: per-month rows,<br />hours per cell (8.00 / 4.00),<br />totals, and remaining as of<br />the end of the selected<br />period.</div>"]
        style 22 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        23["<div style='font-weight: bold'>timesheetDocument</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, pdfmake, src/pdf/timesheetDocument.ts]</div><div style='font-size: 80%; margin-top:10px'>Built-in landscape A4 pdfmake<br />doc definition: contract<br />header repeated on every<br />page, 1 to 31 grid, per-month<br />and grand totals, three<br />signature boxes. Guarded by a<br />golden test.</div>"]
        style 23 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        24["<div style='font-weight: bold'>templateDocument / fillTemplate / templateValues</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, html-to-pdfmake, src/pdf/*.ts]</div><div style='font-size: 80%; margin-top:10px'>HTML template rendering (ADR<br />0010): fills placeholders,<br />converts with<br />html-to-pdfmake, swaps in the<br />generated grid, totals,<br />header and signature blocks,<br />appends the attribution line.</div>"]
        style 24 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        25["<div style='font-weight: bold'>downloadPdf</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, pdfmake, src/pdf/downloadPdf.ts]</div><div style='font-size: 80%; margin-top:10px'>Creates the PDF with pdfmake<br />in the browser and downloads<br />Timesheet_<ContractCode>_<from>_<to>.pdf.</div>"]
        style 25 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
      end

      26("<div style='font-weight: bold'>wp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx web part, React 17, Fluent UI 8, npm workspace @orpheussystems/wp-contract-calendar]</div><div style='font-size: 80%; margin-top:10px'>The contract calendar web<br />part: contract selector,<br />month grids for the contract<br />period, selection type<br />picker, range drag, stats<br />panel, overlap dialog and the<br />timesheet dialog. Runs on<br />demo data in a debug build.</div>")
      style 26 fill:#b8862e,stroke:#805d20,color:#0b0d10
      8["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
      style 8 fill:#5c5c5c,stroke:#404040,color:#ffffff
    end

    26-. "<div>Creates when the environment<br />is Local or useLocalData is<br />on</div><div style='font-size: 70%'></div>" .->8
    26-. "<div>Builds the timesheet model</div><div style='font-size: 70%'></div>" .->22
    26-. "<div>Downloads the PDF</div><div style='font-size: 70%'></div>" .->25
    26-. "<div>Hands the filled template to<br />the renderer</div><div style='font-size: 70%'></div>" .->24
    26-. "<div>Reports results and errors</div><div style='font-size: 70%'></div>" .->21
    26-. "<div>Theme</div><div style='font-size: 70%'></div>" .->20
    25-. "<div>Built-in layout when no<br />template is set</div><div style='font-size: 70%'></div>" .->23
    25-. "<div>Template layout when a path<br />is set</div><div style='font-size: 70%'></div>" .->24
    24-. "<div>Reuses the block builders for<br />grid, totals, header and<br />signatures</div><div style='font-size: 70%'></div>" .->23
    22-. "<div>Totals and hours per day</div><div style='font-size: 70%'></div>" .->8

  end

WebPartComponents

wp-contract-calendar: web part shell, data context and the calendar components.

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Component View: Contract Calendar suite - wp-contract-calendar"]
    style diagram fill:#ffffff,stroke:#ffffff

    64("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
    style 64 fill:#7f7f7f,stroke:#585858,color:#ffffff

    subgraph 7 ["Contract Calendar suite"]
      style 7 fill:#ffffff,stroke:#9c7528,color:#9c7528

      subgraph 26 ["wp-contract-calendar"]
        style 26 fill:#ffffff,stroke:#805d20,color:#805d20

        27["<div style='font-weight: bold'>ContractCalendarWebPart</div><div style='font-size: 70%; margin-top: 0px'>[Component: SPFx BaseClientSideWebPart, ContractCalendarWebPart.ts]</div><div style='font-size: 80%; margin-top:10px'>SPFx entry point and property<br />pane: list pickers filtered<br />by content type,<br />useLocalData, demo user,<br />months to show, Block<br />weekends (applies to the web<br />part's writes; holidays are<br />always blocked; other clients<br />keep blocking weekends, known<br />gap G-1), labels, timesheet<br />template path. Reads the<br />contractId query parameter.</div>"]
        style 27 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        28["<div style='font-weight: bold'>createDataService</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, createDataService.ts]</div><div style='font-size: 80%; margin-top:10px'>Chooses LocalDataService or<br />SharePointDataService once in<br />onInit from the environment<br />and the useLocalData<br />property.</div>"]
        style 28 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        29["<div style='font-weight: bold'>DataContext</div><div style='font-size: 70%; margin-top: 0px'>[Component: React context, DataContext.tsx]</div><div style='font-size: 80%; margin-top:10px'>React context that injects<br />the IDataService into the<br />component tree; no component<br />talks to SharePoint directly.</div>"]
        style 29 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        30["<div style='font-weight: bold'>ContractCalendar</div><div style='font-size: 70%; margin-top: 0px'>[Component: React, components/ContractCalendar.tsx, useCalendarState.ts]</div><div style='font-size: 80%; margin-top:10px'>Root component: header with<br />contract metadata strip,<br />selection type picker, month<br />grids, stats panel;<br />optimistic updates with<br />rollback.</div>"]
        style 30 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        31["<div style='font-weight: bold'>MonthGrid / DayCell / useRangeDrag</div><div style='font-size: 70%; margin-top: 0px'>[Component: React, components/MonthGrid.tsx, DayCell.tsx]</div><div style='font-size: 80%; margin-top:10px'>Monday-first grids, blocked<br />weekends and holidays, click<br />toggles the selected type,<br />drag selects a range.</div>"]
        style 31 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        32["<div style='font-weight: bold'>OverlapDialog</div><div style='font-size: 70%; margin-top: 0px'>[Component: React, Fluent UI Dialog]</div><div style='font-size: 80%; margin-top:10px'>Asks which contract an<br />overlap day counts against<br />before saving.</div>"]
        style 32 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        33["<div style='font-weight: bold'>StatsPanel</div><div style='font-size: 70%; margin-top: 0px'>[Component: React, components/StatsPanel.tsx]</div><div style='font-size: 80%; margin-top:10px'>Full and half counts, used<br />against target, remaining,<br />warning colour when over.</div>"]
        style 33 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        34["<div style='font-weight: bold'>TimesheetDialog</div><div style='font-size: 70%; margin-top: 0px'>[Component: React, components/TimesheetDialog.tsx]</div><div style='font-size: 80%; margin-top:10px'>Period defaults to the<br />contract span and can be<br />trimmed; triggers the PDF<br />build.</div>"]
        style 34 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        35["<div style='font-weight: bold'>templateSource / templateUrl</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, PnPjs files API]</div><div style='font-size: 80%; margin-top:10px'>Fetches the HTML template<br />file and its images from the<br />site on every generation with<br />the viewer's permissions;<br />reads the template columns<br />through shared-core.</div>"]
        style 35 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        36["<div style='font-weight: bold'>filterListsByContentType</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript]</div><div style='font-size: 80%; margin-top:10px'>Property pane list pickers<br />offer only lists carrying the<br />Orpheus content types.</div>"]
        style 36 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
      end

      19["<div style='font-weight: bold'>shared-ui</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, React 17, pdfmake, html-to-pdfmake 2.5, npm workspace @orpheussystems/shared-ui]</div><div style='font-size: 80%; margin-top:10px'>Design tokens and brand, the<br />shared toast stack, the<br />timesheet model and the PDF<br />builders. The PDF code loads<br />in a lazy chunk<br />(orpheus-timesheet-pdf).</div>"]
      style 19 fill:#5c5c5c,stroke:#404040,color:#ffffff
      8["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
      style 8 fill:#5c5c5c,stroke:#404040,color:#ffffff
    end

    27-. "<div>Selects the data service once<br />in onInit</div><div style='font-size: 70%'></div>" .->28
    28-. "<div>Creates when the environment<br />is Local or useLocalData is<br />on</div><div style='font-size: 70%'></div>" .->8
    27-. "<div>Provides the service to the<br />React tree</div><div style='font-size: 70%'></div>" .->29
    27-. "<div>Filters the list pickers by<br />content type</div><div style='font-size: 70%'></div>" .->36
    27-. "<div>Renders</div><div style='font-size: 70%'></div>" .->30
    30-. "<div>Reads and writes through<br />IDataService</div><div style='font-size: 70%'></div>" .->29
    29-. "<div>Exposes</div><div style='font-size: 70%'></div>" .->8
    30-. "<div>Renders the month grids</div><div style='font-size: 70%'></div>" .->31
    30-. "<div>Renders</div><div style='font-size: 70%'></div>" .->33
    30-. "<div>Opens on an overlap day</div><div style='font-size: 70%'></div>" .->32
    30-. "<div>Opens on Generate timesheet</div><div style='font-size: 70%'></div>" .->34
    30-. "<div>Applies blocking, toggle and<br />totals rules</div><div style='font-size: 70%'></div>" .->8
    34-. "<div>Builds the timesheet model</div><div style='font-size: 70%'></div>" .->19
    34-. "<div>Loads the template and its<br />data when a path is set</div><div style='font-size: 70%'></div>" .->35
    35-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->64
    35-. "<div>Reads the template columns of<br />the contract</div><div style='font-size: 70%'></div>" .->8
    35-. "<div>Hands the filled template to<br />the renderer</div><div style='font-size: 70%'></div>" .->19
    30-. "<div>Reports results and errors</div><div style='font-size: 70%'></div>" .->19
    19-. "<div>Totals and hours per day</div><div style='font-size: 70%'></div>" .->8
    8-. "<div>Reads role-filtered<br />contracts, updates used days</div><div style='font-size: 70%'>[PnPjs 4 REST, OData on indexed columns]</div>" .->64

  end

McpServerComponents

mcp-contract-calendar: request pipeline from token validation to audited tool calls (ADR 0011, ADR 0012, ADR 0014). Proposed oversight parts dashed (ADR 0018).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Component View: Contract Calendar agent - mcp-contract-calendar"]
    style diagram fill:#ffffff,stroke:#ffffff

    64("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
    style 64 fill:#7f7f7f,stroke:#585858,color:#ffffff
    7("<div style='font-weight: bold'>Contract Calendar suite</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>SharePoint Framework 1.23.2<br />solution: one web part, three<br />extensions and two shared<br />libraries. Runs entirely in<br />the browser inside SharePoint<br />Online pages; no server<br />component. Tracks on-site and<br />off-site days against<br />procurement contracts and<br />prints the timesheet PDF.</div>")
    style 7 fill:#e0a83a,stroke:#9c7528,color:#0b0d10
    73("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
    style 73 fill:#7f7f7f,stroke:#585858,color:#ffffff

    subgraph 40 ["Contract Calendar agent"]
      style 40 fill:#ffffff,stroke:#6d88a1,color:#6d88a1

      subgraph 42 ["mcp-contract-calendar"]
        style 42 fill:#ffffff,stroke:#204d72,color:#204d72

        43["<div style='font-weight: bold'>http/app</div><div style='font-size: 70%; margin-top: 0px'>[Component: Express 5, src/http/app.ts]</div><div style='font-size: 80%; margin-top:10px'>Express app: POST /mcp with a<br />per-request<br />StreamableHTTPServerTransport,<br />GET /healthz, 405 on GET and<br />DELETE /mcp, 1 MB JSON limit,<br />refuses to start in<br />AUTH_MODE=entra without the<br />auth middleware, optional<br />one-time redacted header log.</div>"]
        style 43 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        44["<div style='font-weight: bold'>auth/authMiddleware + verifyToken</div><div style='font-size: 70%; margin-top: 0px'>[Component: jose 6, src/auth/*.ts]</div><div style='font-size: 80%; margin-top:10px'>Validates the bearer token<br />with jose (RS256): issuer<br />pinned to the tenant in v1<br />and v2 forms, audience equal<br />to API_AUDIENCE or CLIENT_ID.<br />Sets the claims on the<br />request or ends it with 401.</div>"]
        style 44 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        45["<div style='font-weight: bold'>auth/obo</div><div style='font-size: 70%; margin-top: 0px'>[Component: msal-node 3, src/auth/obo.ts]</div><div style='font-size: 80%; margin-top:10px'>On-behalf-of exchange with<br />msal-node for SP_SCOPE. A<br />consent or interaction error<br />becomes 401 with<br />WWW-Authenticate: Bearer so<br />Copilot shows the sign-in<br />prompt.</div>"]
        style 45 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        46["<div style='font-weight: bold'>context/contextFactory</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/context/*.ts]</div><div style='font-size: 80%; margin-top:10px'>Builds the per-request<br />ToolContext: user (oid, upn,<br />SharePoint id), roles,<br />IDataService and contract<br />directory.<br />DATA_MODE=sharepoint creates<br />an SPFI with the OBO token;<br />DATA_MODE=local uses<br />LocalDataService and<br />fixtures.</div>"]
        style 46 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        47["<div style='font-weight: bold'>mcp/createMcpServer</div><div style='font-size: 70%; margin-top: 0px'>[Component: MCP TypeScript SDK, src/mcp/createMcpServer.ts]</div><div style='font-size: 80%; margin-top:10px'>Registers the six tools with<br />zod input schemas and MCP<br />annotations (readOnlyHint on<br />the four read tools,<br />destructiveHint false on<br />book_days, true on<br />remove_days). Wraps every<br />call in runTool.</div>"]
        style 47 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        48["<div style='font-weight: bold'>tools</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/tools/*.ts]</div><div style='font-size: 80%; margin-top:10px'>get_my_contracts,<br />list_contracts, get_calendar,<br />get_holidays, book_days,<br />remove_days. Range<br />orchestration (max 31 days<br />per write, max 366 per read),<br />needs_choice on an overlap<br />day, weekends and holidays<br />always skipped regardless of<br />the web part setting (known<br />gap G-1), per-day results<br />booked / skipped / failed /<br />removed / notFound, error<br />codes not_found, forbidden,<br />invalid_input, internal.</div>"]
        style 48 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        49["<div style='font-weight: bold'>domain/viewer + applyQuery</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/domain/*.ts]</div><div style='font-size: 80%; margin-top:10px'>canBook (contractors only),<br />seesAll (HR and Admin),<br />isVisible per contract,<br />vendor and manager filters,<br />grouping by vendor.</div>"]
        style 49 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        50["<div style='font-weight: bold'>ContractDirectory</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/sharepoint/SharePointContractDirectory.ts, src/domain/InMemoryContractDirectory.ts]</div><div style='font-size: 80%; margin-top:10px'>Read side for the agent:<br />SharePointContractDirectory<br />(OData pre-filter plus<br />in-memory re-check of every<br />item, Vendor column) or<br />InMemoryContractDirectory for<br />fixtures.</div>"]
        style 50 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        51["<div style='font-weight: bold'>sharepoint/agentRoles + createSpfi</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, PnPjs 4, @pnp/nodejs]</div><div style='font-size: 80%; margin-top:10px'>deriveAgentRoles: the web<br />part's role derivation plus<br />the HR group membership;<br />createSpfi builds the PnPjs<br />instance with the delegated<br />token.</div>"]
        style 51 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        52["<div style='font-weight: bold'>audit/audit</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/audit/audit.ts]</div><div style='font-size: 80%; margin-top:10px'>runTool: one JSON line per<br />tool call (time,<br />correlationId, serverVersion,<br />user, tool, args, outcome,<br />errorCode, per-day detail<br />with entry ids, tokenId) to<br />stdout or AUDIT_LOG_PATH. No<br />prompt text.</div>"]
        style 52 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        53["<div style='font-weight: bold'>log/errorLog</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/log/errorLog.ts]</div><div style='font-size: 80%; margin-top:10px'>One error line per internal<br />failure with the correlation<br />id; bearer tokens, JWT-shaped<br />strings and cookies redacted.</div>"]
        style 53 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        54["<div style='font-weight: bold'>config</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/config.ts]</div><div style='font-size: 80%; margin-top:10px'>AUTH_MODE (entra default,<br />none), DATA_MODE (sharepoint<br />default, local), tenant and<br />site values, list URLs,<br />HR_GROUP, AUDIT_LOG_PATH.<br />Rejects none with sharepoint<br />at start-up.</div>"]
        style 54 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        55["<div style='font-weight: bold'>/mcp/oversight endpoint</div><div style='font-size: 70%; margin-top: 0px'>[Component: Express route, planned]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED (ADR 0018). Second<br />MCP endpoint for the Contract<br />Oversight agent: the four<br />read tools plus<br />reconcile_contract_days,<br />get_contract_health,<br />audit_contract_data and the<br />one write tool<br />save_reconciliation. Never<br />registers book_days or<br />remove_days.</div>"]
        style 55 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        56["<div style='font-weight: bold'>domain: reconcile / health / audit</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, planned]</div><div style='font-size: 80%; margin-top:10px'>PROPOSED (ADR 0018). Pure,<br />unit-tested functions for<br />used days against targets,<br />burn rate, runway, health<br />status and completeness<br />score. Thresholds come from<br />configuration after the<br />business agrees them.</div>"]
        style 56 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
      end

      41("<div style='font-weight: bold'>agent-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, JSON manifests]</div><div style='font-size: 80%; margin-top:10px'>Declarative agent package:<br />app manifest 1.30,<br />declarative agent manifest<br />1.8, instruction.txt and one<br />RemoteMCPServer plugin<br />(manifest 2.4) with dynamic<br />tool discovery and Entra SSO<br />(OAuthPluginVault). The<br />instructions require one<br />confirmation before book_days<br />and remove_days. No code, no<br />build; provisioned by the<br />Agents Toolkit.</div>")
      style 41 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
      57[("<div style='font-weight: bold'>Audit sink</div><div style='font-size: 70%; margin-top: 0px'>[Container: JSON lines; Log Analytics in the target]</div><div style='font-size: 80%; margin-top:10px'>Where the per-tool-call JSON<br />lines land: stdout of the mcp<br />compose service or the file<br />in AUDIT_LOG_PATH during the<br />demo; a dedicated Log<br />Analytics table in the Azure<br />target (ADR 0014, ADR 0016).</div>")]
      style 57 fill:#4d4d4d,stroke:#353535,color:#ffffff
    end

    7-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->64
    41-. "<div>MCP over HTTPS, stateless<br />Streamable HTTP POST /mcp,<br />Entra SSO bearer token</div><div style='font-size: 70%'>[HTTPS, JSON-RPC]</div>" .->43
    43-. "<div>Runs before POST /mcp in<br />AUTH_MODE=entra</div><div style='font-size: 70%'></div>" .->44
    44-. "<div>Fetches signing keys (JWKS)<br />and validates issuer and<br />audience</div><div style='font-size: 70%'>[HTTPS]</div>" .->73
    43-. "<div>Builds the per-request<br />context</div><div style='font-size: 70%'></div>" .->46
    46-. "<div>Exchanges the SSO token</div><div style='font-size: 70%'></div>" .->45
    45-. "<div>On-behalf-of exchange for a<br />delegated SharePoint token<br />(AllSites.Write)</div><div style='font-size: 70%'>[OAuth 2.0 OBO]</div>" .->73
    46-. "<div>Derives roles including HR;<br />builds the SPFI</div><div style='font-size: 70%'></div>" .->51
    51-. "<div>Reuses<br />deriveRolesForContracts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    51-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64
    46-. "<div>Creates per request with the<br />OBO token<br />(DATA_MODE=sharepoint)</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    46-. "<div>Creates the contract<br />directory</div><div style='font-size: 70%'></div>" .->50
    46-. "<div>Reads</div><div style='font-size: 70%'></div>" .->54
    43-. "<div>Creates a server and<br />transport per request</div><div style='font-size: 70%'></div>" .->47
    47-. "<div>Dispatches tool calls</div><div style='font-size: 70%'></div>" .->48
    47-. "<div>Wraps every call in runTool</div><div style='font-size: 70%'></div>" .->52
    47-. "<div>Logs internal errors with the<br />correlation id</div><div style='font-size: 70%'></div>" .->53
    48-. "<div>Visibility and write<br />permission checks</div><div style='font-size: 70%'></div>" .->49
    48-. "<div>Reads contracts with vendor<br />for list_contracts and<br />get_my_contracts</div><div style='font-size: 70%'></div>" .->50
    48-. "<div>book_days and remove_days go<br />through upsertEntry and<br />deleteEntry; get_calendar and<br />get_holidays through the<br />reads</div><div style='font-size: 70%'>[TypeScript import]</div>" .->7
    50-. "<div>OData pre-filter plus<br />in-memory re-check of every<br />item; Vendor column</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64
    52-. "<div>Appends one JSON line per<br />tool call</div><div style='font-size: 70%'></div>" .->57
    55-. "<div>PROPOSED: calls the pure<br />functions</div><div style='font-size: 70%'></div>" .->56
    55-. "<div>PROPOSED: same token<br />validation</div><div style='font-size: 70%'></div>" .->44
    55-. "<div>PROPOSED: same audit</div><div style='font-size: 70%'></div>" .->52
    56-. "<div>PROPOSED: save_reconciliation<br />writes ReconciliationStatus,<br />Details, ReconciledAt,<br />ReconciledBy as the user</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->64

  end

MarkADay

Business process 4.1, mark a day: the contractor clicks a day inside an overlap window (TOGAF Phase B, ADR 0008).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Dynamic View: Contract Calendar suite"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 64 ["SharePoint Online"]
      style 64 fill:#ffffff,stroke:#585858,color:#585858

      65[("<div style='font-weight: bold'>Contracts list</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint list]</div><div style='font-size: 80%; margin-top:10px'>Orpheus Contract content<br />type. Code, framework<br />contract, modality,<br />contractor, manager, period,<br />on-site and off-site targets,<br />hours per day, country,<br />status, used days kept by the<br />service, RemainingDays (field<br />customizer target), optional<br />template columns and Vendor.<br />Site-default permissions:<br />contractors hold Contribute<br />(ADR 0005).</div>")]
      style 65 fill:#7f7f7f,stroke:#585858,color:#ffffff
      66[("<div style='font-weight: bold'>CalendarEntries list</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint list]</div><div style='font-size: 80%; margin-top:10px'>One item per contractor per<br />date across all their<br />contracts (ADR 0008): lookup<br />to the contract, EntryDate<br />(indexed), EntryType<br />(OnSiteFull, OnSiteHalf,<br />OffSiteFull, OffSiteHalf),<br />optional note.</div>")]
      style 66 fill:#7f7f7f,stroke:#585858,color:#ffffff
    end

    subgraph 7 ["Contract Calendar suite"]
      style 7 fill:#ffffff,stroke:#9c7528,color:#9c7528

      26("<div style='font-weight: bold'>wp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx web part, React 17, Fluent UI 8, npm workspace @orpheussystems/wp-contract-calendar]</div><div style='font-size: 80%; margin-top:10px'>The contract calendar web<br />part: contract selector,<br />month grids for the contract<br />period, selection type<br />picker, range drag, stats<br />panel, overlap dialog and the<br />timesheet dialog. Runs on<br />demo data in a debug build.</div>")
      style 26 fill:#b8862e,stroke:#805d20,color:#0b0d10
      8["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
      style 8 fill:#5c5c5c,stroke:#404040,color:#ffffff
    end

    1["<div style='font-weight: bold'>Contractor</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Consultant placed on one or<br />more procurement contracts.<br />Marks on-site and off-site<br />days, full or half, and<br />prints the timesheet. Books<br />days through the Copilot<br />agent too.</div>"]
    style 1 fill:#0b0d10,stroke:#07090b,color:#ffffff

    1-. "<div>1. Clicks a day with the<br />selection type on-site full</div><div style='font-size: 70%'>[Browser]</div>" .->26
    26-. "<div>2. Holiday, or weekend with<br />Block weekends on? Already<br />holds the same type? Inside<br />an overlap window?</div><div style='font-size: 70%'></div>" .->8
    26-. "<div>3. Overlap dialog: count<br />against which contract?</div><div style='font-size: 70%'>[Browser]</div>" .->1
    26-. "<div>4. upsertEntry(entry) through<br />IDataService, optimistic UI<br />with rollback</div><div style='font-size: 70%'></div>" .->8
    8-. "<div>5. Deletes any entry on that<br />date on the contractor's<br />other contracts, adds the new<br />one (batched)</div><div style='font-size: 70%'>[PnPjs 4 REST, batched writes]</div>" .->66
    8-. "<div>6. Recomputes OnSiteUsedDays<br />/ OffSiteUsedDays on every<br />affected contract, counting<br />only entries inside the<br />contract period</div><div style='font-size: 70%'>[PnPjs 4 REST, OData on indexed columns]</div>" .->65
    26-. "<div>7. Stats panel refreshes:<br />used, remaining, warning<br />colour</div><div style='font-size: 70%'>[Browser]</div>" .->1

  end

GenerateTimesheet

Business process 4.3, generate the timesheet with an HTML template set (ADR 0004, ADR 0010).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Dynamic View: Contract Calendar suite"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 64 ["SharePoint Online"]
      style 64 fill:#ffffff,stroke:#585858,color:#585858

      69[("<div style='font-weight: bold'>Timesheet HTML template</div><div style='font-size: 70%; margin-top: 0px'>[Container: File in a document library]</div><div style='font-size: 80%; margin-top:10px'>Optional .html file uploaded<br />by a site owner to a library<br />only owners can edit; the web<br />part property points at it<br />(ADR 0010). Read with the<br />viewer's permissions on every<br />generation.</div>")]
      style 69 fill:#7f7f7f,stroke:#585858,color:#ffffff
    end

    subgraph 7 ["Contract Calendar suite"]
      style 7 fill:#ffffff,stroke:#9c7528,color:#9c7528

      19["<div style='font-weight: bold'>shared-ui</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, React 17, pdfmake, html-to-pdfmake 2.5, npm workspace @orpheussystems/shared-ui]</div><div style='font-size: 80%; margin-top:10px'>Design tokens and brand, the<br />shared toast stack, the<br />timesheet model and the PDF<br />builders. The PDF code loads<br />in a lazy chunk<br />(orpheus-timesheet-pdf).</div>"]
      style 19 fill:#5c5c5c,stroke:#404040,color:#ffffff
      26("<div style='font-weight: bold'>wp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx web part, React 17, Fluent UI 8, npm workspace @orpheussystems/wp-contract-calendar]</div><div style='font-size: 80%; margin-top:10px'>The contract calendar web<br />part: contract selector,<br />month grids for the contract<br />period, selection type<br />picker, range drag, stats<br />panel, overlap dialog and the<br />timesheet dialog. Runs on<br />demo data in a debug build.</div>")
      style 26 fill:#b8862e,stroke:#805d20,color:#0b0d10
      8["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
      style 8 fill:#5c5c5c,stroke:#404040,color:#ffffff
    end

    2["<div style='font-weight: bold'>Contract manager</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Manages at least one<br />contract, or is a member of<br />the Orpheus Contract Managers<br />group (Contribute). May<br />create contracts; edits only<br />the contracts they manage.<br />Watches used and remaining<br />days, generates the<br />timesheet. Read-only on<br />calendar days. Reads the<br />contracts they manage by<br />vendor through the agent,<br />read only.</div>"]
    style 2 fill:#0b0d10,stroke:#07090b,color:#ffffff

    2-. "<div>1. Presses Generate<br />timesheet; the period<br />defaults to the contract span</div><div style='font-size: 70%'>[Browser]</div>" .->26
    26-. "<div>2. Entries, holidays and the<br />five template columns of the<br />contract</div><div style='font-size: 70%'></div>" .->8
    26-. "<div>3. Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->69
    26-. "<div>4. Timesheet model, then the<br />template renderer:<br />placeholders filled,<br />html-to-pdfmake, generated<br />grid, totals, header and<br />signatures swapped in,<br />attribution appended</div><div style='font-size: 70%'></div>" .->19
    19-. "<div>5. Downloads<br />Timesheet_<ContractCode>_<from>_<to>.pdf,<br />or shows the error list and<br />downloads nothing if the<br />template is broken</div><div style='font-size: 70%'>[Browser]</div>" .->2

  end

BookDaysViaAgent

A contractor books days in Copilot Chat: SSO, on-behalf-of, shared rules, three audit layers (ADR 0012, ADR 0014).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Dynamic View: Contract Calendar agent - mcp-contract-calendar"]
    style diagram fill:#ffffff,stroke:#ffffff

    1["<div style='font-weight: bold'>Contractor</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Consultant placed on one or<br />more procurement contracts.<br />Marks on-site and off-site<br />days, full or half, and<br />prints the timesheet. Books<br />days through the Copilot<br />agent too.</div>"]
    style 1 fill:#0b0d10,stroke:#07090b,color:#ffffff

    72("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
    style 72 fill:#7f7f7f,stroke:#585858,color:#ffffff
    73("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
    style 73 fill:#7f7f7f,stroke:#585858,color:#ffffff
    74("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
    style 74 fill:#7f7f7f,stroke:#585858,color:#ffffff

    subgraph 40 ["Contract Calendar agent"]
      style 40 fill:#ffffff,stroke:#6d88a1,color:#6d88a1

      subgraph 42 ["mcp-contract-calendar"]
        style 42 fill:#ffffff,stroke:#204d72,color:#204d72

        43["<div style='font-weight: bold'>http/app</div><div style='font-size: 70%; margin-top: 0px'>[Component: Express 5, src/http/app.ts]</div><div style='font-size: 80%; margin-top:10px'>Express app: POST /mcp with a<br />per-request<br />StreamableHTTPServerTransport,<br />GET /healthz, 405 on GET and<br />DELETE /mcp, 1 MB JSON limit,<br />refuses to start in<br />AUTH_MODE=entra without the<br />auth middleware, optional<br />one-time redacted header log.</div>"]
        style 43 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        44["<div style='font-weight: bold'>auth/authMiddleware + verifyToken</div><div style='font-size: 70%; margin-top: 0px'>[Component: jose 6, src/auth/*.ts]</div><div style='font-size: 80%; margin-top:10px'>Validates the bearer token<br />with jose (RS256): issuer<br />pinned to the tenant in v1<br />and v2 forms, audience equal<br />to API_AUDIENCE or CLIENT_ID.<br />Sets the claims on the<br />request or ends it with 401.</div>"]
        style 44 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        45["<div style='font-weight: bold'>auth/obo</div><div style='font-size: 70%; margin-top: 0px'>[Component: msal-node 3, src/auth/obo.ts]</div><div style='font-size: 80%; margin-top:10px'>On-behalf-of exchange with<br />msal-node for SP_SCOPE. A<br />consent or interaction error<br />becomes 401 with<br />WWW-Authenticate: Bearer so<br />Copilot shows the sign-in<br />prompt.</div>"]
        style 45 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        46["<div style='font-weight: bold'>context/contextFactory</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/context/*.ts]</div><div style='font-size: 80%; margin-top:10px'>Builds the per-request<br />ToolContext: user (oid, upn,<br />SharePoint id), roles,<br />IDataService and contract<br />directory.<br />DATA_MODE=sharepoint creates<br />an SPFI with the OBO token;<br />DATA_MODE=local uses<br />LocalDataService and<br />fixtures.</div>"]
        style 46 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        47["<div style='font-weight: bold'>mcp/createMcpServer</div><div style='font-size: 70%; margin-top: 0px'>[Component: MCP TypeScript SDK, src/mcp/createMcpServer.ts]</div><div style='font-size: 80%; margin-top:10px'>Registers the six tools with<br />zod input schemas and MCP<br />annotations (readOnlyHint on<br />the four read tools,<br />destructiveHint false on<br />book_days, true on<br />remove_days). Wraps every<br />call in runTool.</div>"]
        style 47 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        48["<div style='font-weight: bold'>tools</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/tools/*.ts]</div><div style='font-size: 80%; margin-top:10px'>get_my_contracts,<br />list_contracts, get_calendar,<br />get_holidays, book_days,<br />remove_days. Range<br />orchestration (max 31 days<br />per write, max 366 per read),<br />needs_choice on an overlap<br />day, weekends and holidays<br />always skipped regardless of<br />the web part setting (known<br />gap G-1), per-day results<br />booked / skipped / failed /<br />removed / notFound, error<br />codes not_found, forbidden,<br />invalid_input, internal.</div>"]
        style 48 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        49["<div style='font-weight: bold'>domain/viewer + applyQuery</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/domain/*.ts]</div><div style='font-size: 80%; margin-top:10px'>canBook (contractors only),<br />seesAll (HR and Admin),<br />isVisible per contract,<br />vendor and manager filters,<br />grouping by vendor.</div>"]
        style 49 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        51["<div style='font-weight: bold'>sharepoint/agentRoles + createSpfi</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, PnPjs 4, @pnp/nodejs]</div><div style='font-size: 80%; margin-top:10px'>deriveAgentRoles: the web<br />part's role derivation plus<br />the HR group membership;<br />createSpfi builds the PnPjs<br />instance with the delegated<br />token.</div>"]
        style 51 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
        52["<div style='font-weight: bold'>audit/audit</div><div style='font-size: 70%; margin-top: 0px'>[Component: TypeScript, src/audit/audit.ts]</div><div style='font-size: 80%; margin-top:10px'>runTool: one JSON line per<br />tool call (time,<br />correlationId, serverVersion,<br />user, tool, args, outcome,<br />errorCode, per-day detail<br />with entry ids, tokenId) to<br />stdout or AUDIT_LOG_PATH. No<br />prompt text.</div>"]
        style 52 fill:#6b6b6b,stroke:#4a4a4a,color:#ffffff
      end

      41("<div style='font-weight: bold'>agent-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, JSON manifests]</div><div style='font-size: 80%; margin-top:10px'>Declarative agent package:<br />app manifest 1.30,<br />declarative agent manifest<br />1.8, instruction.txt and one<br />RemoteMCPServer plugin<br />(manifest 2.4) with dynamic<br />tool discovery and Entra SSO<br />(OAuthPluginVault). The<br />instructions require one<br />confirmation before book_days<br />and remove_days. No code, no<br />build; provisioned by the<br />Agents Toolkit.</div>")
      style 41 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
      57[("<div style='font-weight: bold'>Audit sink</div><div style='font-size: 70%; margin-top: 0px'>[Container: JSON lines; Log Analytics in the target]</div><div style='font-size: 80%; margin-top:10px'>Where the per-tool-call JSON<br />lines land: stdout of the mcp<br />compose service or the file<br />in AUDIT_LOG_PATH during the<br />demo; a dedicated Log<br />Analytics table in the Azure<br />target (ADR 0014, ADR 0016).</div>")]
      style 57 fill:#4d4d4d,stroke:#353535,color:#ffffff
    end

    1["<div style='font-weight: bold'>Contractor</div><div style='font-size: 70%; margin-top: 0px'>[Person]</div><div style='font-size: 80%; margin-top:10px'>Consultant placed on one or<br />more procurement contracts.<br />Marks on-site and off-site<br />days, full or half, and<br />prints the timesheet. Books<br />days through the Copilot<br />agent too.</div>"]
    style 1 fill:#0b0d10,stroke:#07090b,color:#ffffff
    8["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
    style 8 fill:#5c5c5c,stroke:#404040,color:#ffffff
    41("<div style='font-weight: bold'>agent-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, JSON manifests]</div><div style='font-size: 80%; margin-top:10px'>Declarative agent package:<br />app manifest 1.30,<br />declarative agent manifest<br />1.8, instruction.txt and one<br />RemoteMCPServer plugin<br />(manifest 2.4) with dynamic<br />tool discovery and Entra SSO<br />(OAuthPluginVault). The<br />instructions require one<br />confirmation before book_days<br />and remove_days. No code, no<br />build; provisioned by the<br />Agents Toolkit.</div>")
    style 41 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
    57[("<div style='font-weight: bold'>Audit sink</div><div style='font-size: 70%; margin-top: 0px'>[Container: JSON lines; Log Analytics in the target]</div><div style='font-size: 80%; margin-top:10px'>Where the per-tool-call JSON<br />lines land: stdout of the mcp<br />compose service or the file<br />in AUDIT_LOG_PATH during the<br />demo; a dedicated Log<br />Analytics table in the Azure<br />target (ADR 0014, ADR 0016).</div>")]
    style 57 fill:#4d4d4d,stroke:#353535,color:#ffffff
    66[("<div style='font-weight: bold'>CalendarEntries list</div><div style='font-size: 70%; margin-top: 0px'>[Container: SharePoint list]</div><div style='font-size: 80%; margin-top:10px'>One item per contractor per<br />date across all their<br />contracts (ADR 0008): lookup<br />to the contract, EntryDate<br />(indexed), EntryType<br />(OnSiteFull, OnSiteHalf,<br />OffSiteFull, OffSiteHalf),<br />optional note.</div>")]
    style 66 fill:#7f7f7f,stroke:#585858,color:#ffffff
    72("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
    style 72 fill:#7f7f7f,stroke:#585858,color:#ffffff
    73("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
    style 73 fill:#7f7f7f,stroke:#585858,color:#ffffff
    74("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
    style 74 fill:#7f7f7f,stroke:#585858,color:#ffffff

    1-. "<div>1. Book me on-site days from<br />1 to 5 December</div><div style='font-size: 70%'>[Copilot Chat or Teams]</div>" .->72
    72-. "<div>2. Runs the agent; the<br />instructions restate dates,<br />type and contract and ask for<br />one confirmation</div><div style='font-size: 70%'></div>" .->41
    72-. "<div>3. SSO token for the server's<br />Application ID URI</div><div style='font-size: 70%'>[OAuth 2.0]</div>" .->73
    41-. "<div>4. POST /mcp tools/call<br />book_days, bearer token</div><div style='font-size: 70%'>[HTTPS, JSON-RPC]</div>" .->43
    43-. "<div>5. Validates issuer and<br />audience (jose)</div><div style='font-size: 70%'></div>" .->44
    43-. "<div>6. Builds the request context</div><div style='font-size: 70%'></div>" .->46
    46-. "<div>7. On-behalf-of exchange</div><div style='font-size: 70%'></div>" .->45
    45-. "<div>8. Delegated SharePoint<br />token, AllSites.Write</div><div style='font-size: 70%'>[OAuth 2.0 OBO]</div>" .->73
    46-. "<div>9. Roles: contractor,<br />manager, HR, admin</div><div style='font-size: 70%'></div>" .->51
    43-. "<div>10. Dispatches the call</div><div style='font-size: 70%'></div>" .->47
    47-. "<div>11. book_days(from, to, type,<br />contractId?)</div><div style='font-size: 70%'></div>" .->48
    48-. "<div>12. canBook and isVisible;<br />forbidden for anyone but the<br />contract's contractor</div><div style='font-size: 70%'></div>" .->49
    48-. "<div>13. upsertEntry per business<br />day; weekends, holidays and<br />days outside the contract<br />skipped; needs_choice on an<br />unresolved overlap day</div><div style='font-size: 70%'>[TypeScript import]</div>" .->8
    8-. "<div>14. Writes as the user (OBO)</div><div style='font-size: 70%'>[PnPjs 4 REST, batched writes]</div>" .->66
    66-. "<div>15. ListItemCreated with the<br />real user as actor (layer 2)</div><div style='font-size: 70%'></div>" .->74
    47-. "<div>16. runTool: one JSON line<br />with booked[].entryId,<br />skipped, failed (layer 3)</div><div style='font-size: 70%'></div>" .->52
    52-. "<div>17. Appends</div><div style='font-size: 70%'></div>" .->57
    72-. "<div>18. CopilotInteraction record<br />(layer 1)</div><div style='font-size: 70%'></div>" .->74

  end

ProductionSite

Where the SPFx suite runs: the browser on a SharePoint Online page, the site, and the administrator's host for provisioning (TOGAF Phase D, section 3.1).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Deployment View: SharePoint site"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 314 ["User's browser"]
      style 314 fill:#ffffff,stroke:#444444,color:#444444

      315("<div style='font-weight: bold'>wp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx web part, React 17, Fluent UI 8, npm workspace @orpheussystems/wp-contract-calendar]</div><div style='font-size: 80%; margin-top:10px'>The contract calendar web<br />part: contract selector,<br />month grids for the contract<br />period, selection type<br />picker, range drag, stats<br />panel, overlap dialog and the<br />timesheet dialog. Runs on<br />demo data in a debug build.</div>")
      style 315 fill:#b8862e,stroke:#805d20,color:#0b0d10
      316("<div style='font-weight: bold'>ext-app-customizer</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx application customizer, React 17, npm workspace @orpheussystems/ext-app-customizer]</div><div style='font-size: 80%; margin-top:10px'>Header and footer on every<br />page: Orpheus mark and<br />wordmark, role-aware menu (My<br />calendar, Contracts,<br />Holidays) that collapses<br />below 640 px, footer with<br />contact and solution version.<br />Menu items and list URLs come<br />from<br />ClientSideComponentProperties<br />written by provisioning. The<br />package has no feature; the<br />OrpheusChrome web custom<br />action is registered by<br />OrpheusSolutions.psm1. A<br />failed role check shows a<br />menu without role items.</div>")
      style 316 fill:#b8862e,stroke:#805d20,color:#0b0d10
      317("<div style='font-weight: bold'>ext-field-customizer</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx field customizer, React 17, npm workspace @orpheussystems/ext-field-customizer]</div><div style='font-size: 80%; margin-top:10px'>Remaining-days bars on the<br />RemainingDays column of the<br />Contracts list: on-site and<br />off-site used against target,<br />amber at 80 percent, red when<br />over. Reads only the row's<br />own columns; no extra query<br />per row.</div>")
      style 317 fill:#b8862e,stroke:#805d20,color:#0b0d10
      318("<div style='font-weight: bold'>ext-form-customizer</div><div style='font-size: 70%; margin-top: 0px'>[Container: SPFx form customizer, React 17, Fluent UI 8, npm workspace @orpheussystems/ext-form-customizer]</div><div style='font-size: 80%; margin-top:10px'>New, Edit and Display forms<br />for the Contracts list:<br />validation as you type,<br />people and date pickers,<br />overlap warning, Open<br />calendar link. New: Admin or<br />ContractManager. Edit: Admin<br />or the contract's own<br />manager. If the role check<br />fails, nobody edits (fail<br />closed). When an edit changes<br />the period, it recomputes the<br />used days from the entries<br />inside the new period in the<br />same save and warns about<br />booked days outside it.</div>")
      style 318 fill:#b8862e,stroke:#805d20,color:#0b0d10
      319["<div style='font-weight: bold'>shared-core</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, npm workspace @orpheussystems/shared-core]</div><div style='font-size: 80%; margin-top:10px'>Models, business rules (one<br />entry per contractor per<br />date, holidays always blocked<br />and weekends unless switched<br />off, used days counted inside<br />the contract period only),<br />date handling, role<br />derivation and the<br />IDataService seam with a<br />Local and a SharePoint<br />implementation. Shared by<br />every SPFx package and by the<br />MCP server.</div>"]
      style 319 fill:#5c5c5c,stroke:#404040,color:#ffffff
      323["<div style='font-weight: bold'>shared-ui</div><div style='font-size: 70%; margin-top: 0px'>[Container: TypeScript library, React 17, pdfmake, html-to-pdfmake 2.5, npm workspace @orpheussystems/shared-ui]</div><div style='font-size: 80%; margin-top:10px'>Design tokens and brand, the<br />shared toast stack, the<br />timesheet model and the PDF<br />builders. The PDF code loads<br />in a lazy chunk<br />(orpheus-timesheet-pdf).</div>"]
      style 323 fill:#5c5c5c,stroke:#404040,color:#ffffff
    end

    subgraph 329 ["Microsoft 365"]
      style 329 fill:#ffffff,stroke:#444444,color:#444444

      subgraph 330 ["SharePoint Online site"]
        style 330 fill:#ffffff,stroke:#444444,color:#444444

        331("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
        style 331 fill:#7f7f7f,stroke:#585858,color:#ffffff
      end

      337("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
      style 337 fill:#7f7f7f,stroke:#585858,color:#ffffff
    end

    subgraph 339 ["Administrator's host"]
      style 339 fill:#ffffff,stroke:#444444,color:#444444

      340("<div style='font-weight: bold'>Provisioning scripts</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>PnP PowerShell 3 on<br />PowerShell 7.4, run from the<br />host, idempotent with -WhatIf<br />(a re-run reports Unchanged<br />on every step), tested with<br />Pester against faked cmdlets.<br />No Node needed.</div>")
      style 340 fill:#e8db9c,stroke:#a2996d,color:#0b0d10
    end

    315-. "<div>Creates when the environment<br />is Local or useLocalData is<br />on</div><div style='font-size: 70%'></div>" .->319
    316-. "<div>Role derivation<br />(deriveRolesForContracts,<br />resolveListIdByUrl), menu<br />link helpers</div><div style='font-size: 70%'>[TypeScript import]</div>" .->319
    318-. "<div>Models, mappers, overlap<br />query,<br />deriveRolesForContracts<br />(fails closed)</div><div style='font-size: 70%'>[TypeScript import]</div>" .->319
    315-. "<div>Builds the timesheet model</div><div style='font-size: 70%'></div>" .->323
    316-. "<div>Theme tokens, brand, toasts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->323
    317-. "<div>Theme tokens</div><div style='font-size: 70%'>[TypeScript import]</div>" .->323
    318-. "<div>Theme tokens, toasts</div><div style='font-size: 70%'>[TypeScript import]</div>" .->323
    323-. "<div>Totals and hours per day</div><div style='font-size: 70%'></div>" .->319
    315-. "<div>Fetches the HTML file and<br />images with the viewer's<br />permissions, no cache</div><div style='font-size: 70%'>[PnPjs files API]</div>" .->331
    316-. "<div>Reads the user's contracts to<br />derive roles; fails open to a<br />role-less menu</div><div style='font-size: 70%'>[PnPjs 4 REST]</div>" .->331
    317-. "<div>Renders RemainingDays from<br />the row's own target and used<br />columns; no extra query</div><div style='font-size: 70%'>[SPFx field customizer binding]</div>" .->331
    318-. "<div>Loads, validates and saves<br />contracts; queries<br />overlapping contracts of the<br />same contractor</div><div style='font-size: 70%'>[PnPjs 4 REST]</div>" .->331
    319-. "<div>Reads role-filtered<br />contracts, updates used days</div><div style='font-size: 70%'>[PnPjs 4 REST, OData on indexed columns]</div>" .->331
    331-. "<div>ListItem* records with the<br />real user as actor (layer 2,<br />automatic)</div><div style='font-size: 70%'></div>" .->337
    340-. "<div>Registers the OrpheusChrome<br />web custom action once, with<br />list and page URLs in<br />ClientSideComponentProperties</div><div style='font-size: 70%'>[PnP PowerShell, REST UserCustomActions]</div>" .->316
    340-. "<div>Binds to<br />Contracts.RemainingDays and<br />adds the column to the<br />default view</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->317
    340-. "<div>Binds to the New, Edit and<br />Display forms of Orpheus<br />Contract</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->318
    340-. "<div>Creates content type, list,<br />indexes, RemainingDays, list<br />validation; -SeedDemoData<br />upserts demo contracts</div><div style='font-size: 70%'>[PnP PowerShell, Invoke-PnPSiteTemplate]</div>" .->331

  end

AgentDemo

The agent demo: Podman service mcp behind a persistent dev tunnel on the developer host; nothing in Azure (ADR 0015, Phase D section 3.2).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Deployment View: Agent demo (dev tunnel)"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 345 ["Microsoft cloud"]
      style 345 fill:#ffffff,stroke:#444444,color:#444444

      subgraph 346 ["Microsoft 365 Copilot"]
        style 346 fill:#ffffff,stroke:#444444,color:#444444

        347("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
        style 347 fill:#7f7f7f,stroke:#585858,color:#ffffff
        348("<div style='font-weight: bold'>agent-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, JSON manifests]</div><div style='font-size: 80%; margin-top:10px'>Declarative agent package:<br />app manifest 1.30,<br />declarative agent manifest<br />1.8, instruction.txt and one<br />RemoteMCPServer plugin<br />(manifest 2.4) with dynamic<br />tool discovery and Entra SSO<br />(OAuthPluginVault). The<br />instructions require one<br />confirmation before book_days<br />and remove_days. No code, no<br />build; provisioned by the<br />Agents Toolkit.</div>")
        style 348 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
      end

      350("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
      style 350 fill:#7f7f7f,stroke:#585858,color:#ffffff
      subgraph 352 ["SharePoint Online (dev tenant site)"]
        style 352 fill:#ffffff,stroke:#444444,color:#444444

        353("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
        style 353 fill:#7f7f7f,stroke:#585858,color:#ffffff
      end

      354("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
      style 354 fill:#7f7f7f,stroke:#585858,color:#ffffff
      357("<div style='font-weight: bold'>Microsoft dev tunnel relay</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Persistent tunnel with a<br />stable host name, anonymous<br />at the relay because Copilot<br />cannot sign in to it. Demo<br />only, no SLA (ADR 0015).</div>")
      style 357 fill:#7f7f7f,stroke:#585858,color:#ffffff
    end

    subgraph 358 ["Developer host"]
      style 358 fill:#ffffff,stroke:#444444,color:#444444

      359["<div style='font-weight: bold'>devtunnel host</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: devtunnel CLI]</div><div style='font-size: 80%; margin-top:10px'>Forwards the persistent<br />tunnel to localhost:8787.<br />Runs on the host OS, stopped<br />when not demoing.</div>"]
      style 359 fill:#ffffff,stroke:#444444,color:#444444
      subgraph 360 ["Podman machine"]
        style 360 fill:#ffffff,stroke:#444444,color:#444444

        subgraph 361 ["default compose project, service mcp"]
          style 361 fill:#ffffff,stroke:#444444,color:#444444

          362["<div style='font-weight: bold'>mcp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Node 22, TypeScript 5.8, Express 5, MCP TypeScript SDK 1.x, zod, jose 6, msal-node 3, PnPjs 4 with @pnp/nodejs]</div><div style='font-size: 80%; margin-top:10px'>MCP server, the agent's<br />action layer. Stateless<br />Streamable HTTP on port 8787:<br />POST /mcp, GET /healthz.<br />Validates the Entra SSO<br />token, exchanges it on behalf<br />of the user for a delegated<br />SharePoint token<br />(AllSites.Write), derives<br />roles including HR, enforces<br />visibility and the<br />contractor-only write rule,<br />writes one JSON audit line<br />per tool call. Reuses<br />shared-core unchanged.</div>"]
          style 362 fill:#2f6fa3,stroke:#204d72,color:#ffffff
          366[("<div style='font-weight: bold'>Audit sink</div><div style='font-size: 70%; margin-top: 0px'>[Container: JSON lines; Log Analytics in the target]</div><div style='font-size: 80%; margin-top:10px'>Where the per-tool-call JSON<br />lines land: stdout of the mcp<br />compose service or the file<br />in AUDIT_LOG_PATH during the<br />demo; a dedicated Log<br />Analytics table in the Azure<br />target (ADR 0014, ADR 0016).</div>")]
          style 366 fill:#4d4d4d,stroke:#353535,color:#ffffff
        end

      end

      subgraph 368 ["PowerShell 7.4"]
        style 368 fill:#ffffff,stroke:#444444,color:#444444

        369("<div style='font-weight: bold'>Setup-AgentDemo.ps1</div><div style='font-size: 70%; margin-top: 0px'>[Container: PnP PowerShell 3, packages/mcp-contract-calendar/setup]</div><div style='font-size: 80%; margin-top:10px'>Agent demo setup: Vendor<br />column with the same fixed id<br />as the template script, the<br />HR SharePoint group with<br />Read, 002xxx demo contracts<br />and entries, recomputed used<br />days (ADR 0013). UPNs are<br />parameters, never committed.</div>")
        style 369 fill:#b5a74a,stroke:#7e7433,color:#0b0d10
      end

    end

    347-. "<div>Runs the declarative agent;<br />reads tools and schemas from<br />the server at runtime<br />(dynamic discovery)</div><div style='font-size: 70%'></div>" .->348
    347-. "<div>Obtains the SSO token for the<br />server's Application ID URI</div><div style='font-size: 70%'>[OAuth 2.0]</div>" .->350
    347-. "<div>CopilotInteraction record per<br />prompt (layer 1, automatic)</div><div style='font-size: 70%'></div>" .->354
    353-. "<div>ListItem* records with the<br />real user as actor (layer 2,<br />automatic)</div><div style='font-size: 70%'></div>" .->354
    348-. "<div>MCP over HTTPS, stateless<br />Streamable HTTP POST /mcp,<br />Entra SSO bearer token</div><div style='font-size: 70%'>[HTTPS, JSON-RPC]</div>" .->362
    362-. "<div>Fetches signing keys (JWKS)<br />and validates issuer and<br />audience</div><div style='font-size: 70%'>[HTTPS]</div>" .->350
    362-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->353
    362-. "<div>Appends one JSON line per<br />tool call</div><div style='font-size: 70%'></div>" .->366
    369-. "<div>Adds Vendor (same fixed id),<br />002xxx demo contracts,<br />recomputes used days</div><div style='font-size: 70%'>[PnP PowerShell]</div>" .->353

  end

ContinuousIntegration

GitHub Actions: the reusable build.yml pipeline and the release job. Builds, tests and packages; never deploys to a tenant (docs/ci.md).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Deployment View: CI (GitHub Actions)"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 371 ["GitHub"]
      style 371 fill:#ffffff,stroke:#444444,color:#444444

      372("<div style='font-weight: bold'>GitHub</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the public repository.<br />GitHub Actions: ci.yml on<br />push and pull request to main<br />runs build.yml (hygiene scan,<br />build, typecheck, production<br />package and tests in<br />docker.io/m365pnp/spfx:1.23.2,<br />Pester on pwsh); release.yml<br />attaches the four .sppkg to<br />v* releases. Dependabot<br />updates the GitHub Actions<br />monthly. A ruleset protects<br />main. Never deploys to a<br />tenant.</div>")
      style 372 fill:#3a3a3a,stroke:#282828,color:#ffffff
      subgraph 373 ["ubuntu-latest runner"]
        style 373 fill:#ffffff,stroke:#444444,color:#444444

        374["<div style='font-weight: bold'>Job node</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: docker.io/m365pnp/spfx:1.23.2, Node 22]</div><div style='font-size: 80%; margin-top:10px'>Hygiene scan<br />(scripts/ci/hygiene.js), npm<br />ci, build:shared, typecheck,<br />package:solutions<br />(production), test:all;<br />uploads the sppkg artifact.</div>"]
        style 374 fill:#ffffff,stroke:#444444,color:#444444
        375["<div style='font-weight: bold'>Job powershell</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: pwsh]</div><div style='font-size: 80%; margin-top:10px'>PnP.PowerShell 3 and Pester 5<br />from the Gallery;<br />scripts/ci/run-pester.ps1<br />over infra/provisioning/tests<br />and packages/*/setup. Every<br />PnP cmdlet is faked.</div>"]
        style 375 fill:#ffffff,stroke:#444444,color:#444444
        376["<div style='font-weight: bold'>Job publish (release.yml)</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: gh CLI]</div><div style='font-size: 80%; margin-top:10px'>Creates the GitHub Release<br />for a v* tag with the four<br />.sppkg attached. Only job<br />with contents write.</div>"]
        style 376 fill:#ffffff,stroke:#444444,color:#444444
      end

    end

    374-. "<div>sppkg artifact</div><div style='font-size: 70%'>[actions/upload-artifact]</div>" .->376

  end

AgentAzureTarget

PROPOSED, not built: API Management in front of Container Apps, Key Vault, Log Analytics audit table, Terraform (ADR 0016, roadmap RM-4).

graph LR
  linkStyle default fill:#ffffff

  subgraph diagram ["Deployment View: Agent Azure target (Proposed, ADR 0016)"]
    style diagram fill:#ffffff,stroke:#ffffff

    subgraph 378 ["Microsoft cloud"]
      style 378 fill:#ffffff,stroke:#444444,color:#444444

      subgraph 379 ["Microsoft 365 Copilot"]
        style 379 fill:#ffffff,stroke:#444444,color:#444444

        380("<div style='font-weight: bold'>Microsoft 365 Copilot Chat</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Hosts the declarative agent<br />in Copilot Chat or Teams.<br />Calls the MCP plugin from<br />Microsoft's cloud over the<br />public internet with an Entra<br />SSO token. Works for<br />unlicensed users through the<br />share link (ADR 0017).</div>")
        style 380 fill:#7f7f7f,stroke:#585858,color:#ffffff
        381("<div style='font-weight: bold'>agent-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Microsoft 365 Agents Toolkit project, JSON manifests]</div><div style='font-size: 80%; margin-top:10px'>Declarative agent package:<br />app manifest 1.30,<br />declarative agent manifest<br />1.8, instruction.txt and one<br />RemoteMCPServer plugin<br />(manifest 2.4) with dynamic<br />tool discovery and Entra SSO<br />(OAuthPluginVault). The<br />instructions require one<br />confirmation before book_days<br />and remove_days. No code, no<br />build; provisioned by the<br />Agents Toolkit.</div>")
        style 381 fill:#5a8fbf,stroke:#3e6485,color:#ffffff
      end

      383("<div style='font-weight: bold'>Microsoft Entra ID</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Issues the SSO token for the<br />server's Application ID URI<br />and performs the on-behalf-of<br />exchange for a delegated<br />SharePoint token. The server<br />has its own app registration,<br />separate from the<br />provisioning one.</div>")
      style 383 fill:#7f7f7f,stroke:#585858,color:#ffffff
      subgraph 385 ["SharePoint Online (client tenant)"]
        style 385 fill:#ffffff,stroke:#444444,color:#444444

        386("<div style='font-weight: bold'>SharePoint Online</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>The Microsoft 365 site that<br />hosts the lists, the calendar<br />page, the optional timesheet<br />template and the site<br />collection App Catalog.<br />Authentication and audit are<br />SharePoint's own.</div>")
        style 386 fill:#7f7f7f,stroke:#585858,color:#ffffff
      end

      387("<div style='font-weight: bold'>Microsoft Purview Audit</div><div style='font-size: 70%; margin-top: 0px'>[Software System]</div><div style='font-size: 80%; margin-top:10px'>Records CopilotInteraction<br />events (layer 1) and<br />SharePoint ListItemCreated /<br />Updated / Deleted events with<br />the real user as actor (layer<br />2). Joined with the server's<br />audit line on user and time<br />(ADR 0014).</div>")
      style 387 fill:#7f7f7f,stroke:#585858,color:#ffffff
    end

    subgraph 390 ["Azure subscription"]
      style 390 fill:#ffffff,stroke:#444444,color:#444444

      391["<div style='font-weight: bold'>API Management</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: Azure API Management]</div><div style='font-size: 80%; margin-top:10px'>Only public entry.<br />validate-jwt on issuer and<br />audience, per-user rate limit<br />keyed on oid. Defence in<br />depth; the server still<br />validates.</div>"]
      style 391 fill:#ffffff,stroke:#444444,color:#444444
      subgraph 392 ["Container Apps environment"]
        style 392 fill:#ffffff,stroke:#444444,color:#444444

        subgraph 393 ["Container App: mcp-contract-calendar"]
          style 393 fill:#ffffff,stroke:#444444,color:#444444

          394["<div style='font-weight: bold'>mcp-contract-calendar</div><div style='font-size: 70%; margin-top: 0px'>[Container: Node 22, TypeScript 5.8, Express 5, MCP TypeScript SDK 1.x, zod, jose 6, msal-node 3, PnPjs 4 with @pnp/nodejs]</div><div style='font-size: 80%; margin-top:10px'>MCP server, the agent's<br />action layer. Stateless<br />Streamable HTTP on port 8787:<br />POST /mcp, GET /healthz.<br />Validates the Entra SSO<br />token, exchanges it on behalf<br />of the user for a delegated<br />SharePoint token<br />(AllSites.Write), derives<br />roles including HR, enforces<br />visibility and the<br />contractor-only write rule,<br />writes one JSON audit line<br />per tool call. Reuses<br />shared-core unchanged.</div>"]
          style 394 fill:#2f6fa3,stroke:#204d72,color:#ffffff
        end

      end

      398["<div style='font-weight: bold'>Container Registry</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: Azure Container Registry]</div><div style='font-size: 80%; margin-top:10px'>Image pulled with the managed<br />identity.</div>"]
      style 398 fill:#ffffff,stroke:#444444,color:#444444
      399["<div style='font-weight: bold'>Key Vault</div><div style='font-size: 70%; margin-top: 0px'>[Infrastructure Node: Azure Key Vault]</div><div style='font-size: 80%; margin-top:10px'>OBO client secret or,<br />preferably, a certificate. No<br />secret in app settings.</div>"]
      style 399 fill:#ffffff,stroke:#444444,color:#444444
      subgraph 400 ["Log Analytics workspace"]
        style 400 fill:#ffffff,stroke:#444444,color:#444444

        401[("<div style='font-weight: bold'>Audit sink</div><div style='font-size: 70%; margin-top: 0px'>[Container: JSON lines; Log Analytics in the target]</div><div style='font-size: 80%; margin-top:10px'>Where the per-tool-call JSON<br />lines land: stdout of the mcp<br />compose service or the file<br />in AUDIT_LOG_PATH during the<br />demo; a dedicated Log<br />Analytics table in the Azure<br />target (ADR 0014, ADR 0016).</div>")]
        style 401 fill:#4d4d4d,stroke:#353535,color:#ffffff
      end

    end

    380-. "<div>Runs the declarative agent;<br />reads tools and schemas from<br />the server at runtime<br />(dynamic discovery)</div><div style='font-size: 70%'></div>" .->381
    380-. "<div>Obtains the SSO token for the<br />server's Application ID URI</div><div style='font-size: 70%'>[OAuth 2.0]</div>" .->383
    380-. "<div>CopilotInteraction record per<br />prompt (layer 1, automatic)</div><div style='font-size: 70%'></div>" .->387
    386-. "<div>ListItem* records with the<br />real user as actor (layer 2,<br />automatic)</div><div style='font-size: 70%'></div>" .->387
    381-. "<div>MCP over HTTPS, stateless<br />Streamable HTTP POST /mcp,<br />Entra SSO bearer token</div><div style='font-size: 70%'>[HTTPS, JSON-RPC]</div>" .->394
    394-. "<div>Fetches signing keys (JWKS)<br />and validates issuer and<br />audience</div><div style='font-size: 70%'>[HTTPS]</div>" .->383
    394-. "<div>Checks the HR group, Orpheus<br />Contract Managers,<br />IsSiteAdmin and the Owners<br />group</div><div style='font-size: 70%'>[PnPjs 4 REST as the user]</div>" .->386
    394-. "<div>Appends one JSON line per<br />tool call</div><div style='font-size: 70%'></div>" .->401

  end

TOGAF phases

Architecture Decision Records