Skip to content

Releases: ph7x-Systems/m365-governance-as-code

1.0.0b6

1.0.0b6 Pre-release
Pre-release

Choose a tag to compare

@jtlivio jtlivio released this 17 Aug 10:46
3774330

Publishes connection/1.0.0, so a consumer stops parsing an unversioned surface.

connect --format json was declared deliberately unversioned, on the reasoning that a session ends when the process does and so has nothing to persist. That answers the wrong question: persistence is not the test, dependence is. A consumer already parses it to decide whether a collection may start, and a shape somebody depends on is a contract whether or not it is called one.

  • connection/1.0.0, with its generated model, published in the bundle.
  • It describes an attempt rather than a resource: no rule reads it, no assessment carries it, it produces no finding.
  • address and session stay separate blocks, and the schema states that observed_tenant_id may never be filled from resolved_tenant_id.
  • address.host is null when nothing was resolved, found by validating an attempt where the collector never ran.

Install

pipx install m365-governance-as-code==1.0.0b6

1.0.0b5

1.0.0b5 Pre-release
Pre-release

Choose a tag to compare

@jtlivio jtlivio released this 16 Aug 18:23
6be20e6

Two commands' worth of honesty about reaching a tenant, and the release gate
that should have existed before 1.0.0b4 went out.

connect: the other half of doctor

doctor reports whether this installation is sound. Nothing reported whether
the application registration in front of you can reach the tenant in front of
you — that was found out several minutes into a collection, from a failure that
looked like a tenant problem rather than a consent problem.

Four words rather than a boolean: established, refused, unreachable,
cancelled. A tenant that answered and would not have us is a different
sentence from one that never answered, and collapsing them sends a person to
check their network when the answer was consent.

Two questions, and one field would answer neither

Address resolution
  <host>  owned by  <directory id>
  Public discovery, and no session was involved.

Authenticated session
  identity   delegated
  observed   not established

Which directory owns an address is answerable by anybody, from public
OpenID discovery, without a token. Which directory a session operated in is
answerable only by the session. A GUID the whole world can obtain without
reaching a tenant is not evidence that a collection looked at it, so
resolved_tenant_id and observed_tenant_id are separate and the second is
null.

The address resolves before the sign-in and is reported whatever the sign-in
does: a tenant that refuses you still has an address, and somebody diagnosing a
consent problem is helped by knowing which directory they were actually pointed
at.

Evidence provenance gains neither by shortcut. That is a change to the evidence
contract, with its own decision.

The gate that was missing after the upload

1.0.0b2 shipped an install command that resolved to nothing. 1.0.0b4
shipped one naming 1.0.0b3. Both uploads succeeded, and a release description
is frozen at upload, so neither could be corrected without spending another
version.

A successful upload proves the file arrived. It proves nothing about whether
anybody can install and run it.

tools/post-release-check.sh installs from the public index into a
throwaway environment and refuses unless the program reports the released
version, doctor is sound, packaged evidence evaluates and decides something,
the documented commands run, and the contract bundle is in the wheel.
publish.yml runs it after every publish.

pipx, with ensurepath

pip install is the wrong command for an application, and on Homebrew's
Python, Debian's and Ubuntu's it is not even possible (PEP 668). Skipping
ensurepath installs successfully and still gives command not found, which
reads like a broken package.

The measured surface stopped counting comments as calls

tools/surface.py decided what the collector calls with a regular expression
over its source, so a comment explaining why a cmdlet was not called made the
published document say it was. It parses the syntax tree now, as the read-only
gate always has.

Status, measured

20 rules, a thirteen-mode collector, 9 profiles, 14 commands, 823 tests at 90
per cent coverage.

Install

brew install pipx        # macOS. Ubuntu 23.04+/Debian 12+: sudo apt install pipx
pipx ensurepath          # then open a new shell

pipx install m365-governance-as-code==1.0.0b5

1.0.0b4

1.0.0b4 Pre-release
Pre-release

Choose a tag to compare

@jtlivio jtlivio released this 16 Aug 16:18
f0aea24

A collection now reports what it is doing while it does it, and says what it
managed to do when it stops. Both were missing, and the second one is a new
contract.

A collection had no account of itself. The only thing a caller could read
was a process exit code, and a run that reached two hundred of three hundred
sites and then lost its connection had the same value as one that never
authenticated. The first produced evidence worth two hundred sites. That
collapse is made nowhere else here: coverage keeps requested and completed
apart, and a rule answers unknown rather than failing when the gap could
change its answer.

A new contract: collection/1.0.0

A collection-manifest.json is written beside the evidence, on every path
including the failure that produced no evidence at all. It carries the state,
the facts behind it, what was asked for, what was observed, the identity that
looked, the coverage as a union of the artefacts' own, every artefact with a
digest over its bytes, and a digest over itself.

Why a manifest, and not a field in each evidence document. An evidence
document is about one resource and a collection is a batch, so a field in each
would be one truth written once per document — and it could not answer the case
the contract exists for: a collection that stops halfway has to say what it did
not read, and the documents that would carry that sentence are exactly the ones
that were never written.

Full contract: docs/COLLECTION-MANIFEST.md

Consumers re-vendor. A collection state that lived only in a consumer would
be that consumer's opinion about an exit code.

Also in this release

  • Four states where there was a boolean: completed, partial, failed,
    cancelled. partial is not a failure and collect exits 0 for it.
    cancelled is set by the caller and never inferred from an exit code.
  • The collector's output streams. It was buffered until the process exited,
    so collect sites against a large tenant printed nothing for however long it
    took and then printed everything, including the line saying how many sites
    the identity had enumerated.
  • evaluate states the bound before the results where a manifest exists,
    on stderr. Where none exists it says nothing: evidence collected before this
    contract carries no account of its own completeness, and inventing one would
    report a gap that was never measured as an absence of gaps.
  • Two collections into one directory no longer destroy each other's record.
  • The canonical form moved to m365_governance/canonical.py, because two
    documents now publish a digest a recipient recomputes.

Defects this found

Three came from printing a manifest the code had just written, and none was
visible in the code: a clean exit that wrote nothing reported completed; a
dry run had a state at all; and the reason an area was not read arrived as a
Python dict repr where a sentence belonged.

One came from adding the schema: the model generator emitted three records
called Versions and two called Coverage into one namespace, all different
shapes. Every file individually correct, the bundle would not have compiled —
which nothing here would have said, because nothing here compiles it. It
refuses now.

Not in this release

An assessment still does not record which collection produced its evidence or
in what state that collection ended. That costs an assessment contract version
and is the next step.

Install

pip install m365-governance-as-code==1.0.0b4

1.0.0b3

1.0.0b3 Pre-release
Pre-release

Choose a tag to compare

@jtlivio jtlivio released this 16 Aug 11:46
d3d9cfa

The engine reports the version that built it, and signs its assessments with it.

1.0.0b2 answered 1.0.0b1, because __version__ was a literal in __init__.py beside the one in pyproject.toml and only one of them was bumped.

That value is part of the canonical content an assessment is digested over. A document produced by 1.0.0b2 therefore carried a correct digest over content naming the wrong engine: the digest was honest about what it covered, and what it covered was not honest about what made it. In an engine whose whole claim is that a conclusion traces back to what produced it, that is not a typo.

The version is now read from the installed distribution metadata, so pyproject.toml is the only place it exists. tests/test_version.py compares the two by import and through --version as a user runs it, and refuses to let the not-installed fallback pass as a real version. It failed the moment it was written, against a virtual environment still holding the previous install.

Also in this release:

  • The generated contract manifest and the stored example assessment and comparison carry 1.0.0b3. Both were stale for the same reason.
  • CONTRIBUTING.md names the whole gate. CI runs tools/release-check.sh, nine steps; the document listed four, so a contributor could run everything it asked for and still be refused for formatting.
  • The repository speaks English. A test file, a comment in tools/coverage.py and the same note repeated across seven PowerShell modules were in Portuguese.

1.0.0b1 and 1.0.0b2 stay in the history. Neither is withdrawn and neither number is reused.

Install

pip install m365-governance-as-code==1.0.0b3

1.0.0b2

1.0.0b2 Pre-release
Pre-release

Choose a tag to compare

@jtlivio jtlivio released this 16 Aug 11:13
965bce3

The release whose project page is right. Nothing in the engine changed.

1.0.0b1 proved the publishing path end to end, and in doing so showed what the path had not checked: the README is what PyPI renders as the project description, and it was written for GitHub.

  • The banner used a relative path. GitHub resolves it against the repository; PyPI has none, so the page opened with a broken image where the product's name belongs.
  • Eighteen more links pointed at CONTRIBUTING, LICENSE, CHANGELOG and ten documents under docs/ by relative path. Every one of them was dead on the project page.
  • pip install m365-governance-as-code resolves to nothing while the only version is a pre-release, and that was the first command anybody was given.
  • The README claimed doctor prints where the packaged fixtures live. It does not, so the quick start reached evaluate --evidence <a file> with no file.

A release description on PyPI is frozen at upload, so correcting the files could not correct the published page. This version is what publishes the corrected one.

Also added: badges for the published version, the Python versions CI runs the suite against, the licence, the state of main, the documentation, the current release and the DCO requirement, each with a line saying what it reports and what it does not. And docs/RELEASING.md, which writes down the publishing procedure and the two things that cannot be fixed after an upload.

Install

pip install m365-governance-as-code==1.0.0b2

The == is not optional: 1.0.0b2 is a pre-release, and pip skips those unless a version is pinned or --pre is given.

1.0.0b1

1.0.0b1 Pre-release
Pre-release

Choose a tag to compare

@jtlivio jtlivio released this 16 Aug 10:50
332dc84

The first public build. A beta, and the version number says so: this is the release that proves the whole path from a tag to pip install, and everything in it has been run rather than described.

What it is

Microsoft 365 governance checks that show their work. Every rule declares what kind of truth it is, and missing evidence is never a pass.

  • 20 rules, each declaring its basis: a limit Microsoft publishes, guidance Microsoft gives, a convention, or an opinion. A report that mixes those is a report that loses the argument it should win.
  • 10 collection slices, read only. Nothing under the collector tree calls a mutating cmdlet, and CI proves it by parsing every file in the tree.
  • 13 commands, from collect through assess to verify, so somebody who receives an assessment can check it without this engine and without trusting whoever sent it.
  • unknown is a result, not a pass. Missing evidence is a fact about collection, not about the resource, and the report says so in those words.

Install

pip install m365-governance-as-code==1.0.0b1

The == is not optional yet: 1.0.0b1 is a pre-release, and pip skips those unless asked.

Python 3.11 or later. PowerShell 7 and PnP.PowerShell are needed only to collect from a tenant; the engine, the rules and the tests run offline against packaged fixtures.

Documentation

The manual is at https://ph7x.com/tools/m365-governance-as-code/docs/ and covers installation, reading a report, every collector, the rule model, every command, the architecture and the trust model.

Known boundaries

Stated here because a governance tool that implies coverage it does not have is worse than one that says nothing. This release does not detect SharePoint Add-in authentication, ACS principals, SharePoint 2013 workflows, or InfoPath forms. Those remain manual discovery.